async_graphql/
request.rs

1use std::{
2    any::Any,
3    fmt::{self, Debug, Formatter},
4};
5
6use serde::{Deserialize, Deserializer, Serialize};
7
8use crate::{
9    parser::{parse_query, types::ExecutableDocument},
10    schema::IntrospectionMode,
11    Data, Extensions, ParseRequestError, ServerError, UploadValue, Value, Variables,
12};
13
14/// GraphQL request.
15///
16/// This can be deserialized from a structure of the query string, the operation
17/// name and the variables. The names are all in `camelCase` (e.g.
18/// `operationName`).
19#[non_exhaustive]
20#[derive(Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct Request {
23    /// The query source of the request.
24    #[serde(default)]
25    pub query: String,
26
27    /// The operation name of the request.
28    #[serde(default, rename = "operationName")]
29    pub operation_name: Option<String>,
30
31    /// The variables of the request.
32    #[serde(default)]
33    pub variables: Variables,
34
35    /// Uploads sent with the request.
36    #[serde(skip)]
37    pub uploads: Vec<UploadValue>,
38
39    /// The data of the request that can be accessed through `Context::data`.
40    ///
41    /// **This data is only valid for this request**
42    #[serde(skip)]
43    pub data: Data,
44
45    /// The extensions config of the request.
46    #[serde(default)]
47    pub extensions: Extensions,
48
49    #[serde(skip)]
50    pub(crate) parsed_query: Option<ExecutableDocument>,
51
52    /// Sets the introspection mode for this request (defaults to
53    /// [IntrospectionMode::Enabled]).
54    #[serde(skip)]
55    pub introspection_mode: IntrospectionMode,
56}
57
58impl Request {
59    /// Create a request object with query source.
60    pub fn new(query: impl Into<String>) -> Self {
61        Self {
62            query: query.into(),
63            operation_name: None,
64            variables: Variables::default(),
65            uploads: Vec::default(),
66            data: Data::default(),
67            extensions: Default::default(),
68            parsed_query: None,
69            introspection_mode: IntrospectionMode::Enabled,
70        }
71    }
72
73    /// Specify the operation name of the request.
74    #[must_use]
75    pub fn operation_name<T: Into<String>>(self, name: T) -> Self {
76        Self {
77            operation_name: Some(name.into()),
78            ..self
79        }
80    }
81
82    /// Specify the variables.
83    #[must_use]
84    pub fn variables(self, variables: Variables) -> Self {
85        Self { variables, ..self }
86    }
87
88    /// Insert some data for this request.
89    #[must_use]
90    pub fn data<D: Any + Send + Sync>(mut self, data: D) -> Self {
91        self.data.insert(data);
92        self
93    }
94
95    /// Disable introspection queries for this request.
96    #[must_use]
97    pub fn disable_introspection(mut self) -> Self {
98        self.introspection_mode = IntrospectionMode::Disabled;
99        self
100    }
101
102    /// Only allow introspection queries for this request.
103    #[must_use]
104    pub fn only_introspection(mut self) -> Self {
105        self.introspection_mode = IntrospectionMode::IntrospectionOnly;
106        self
107    }
108
109    #[inline]
110    /// Performs parsing of query ahead of execution.
111    ///
112    /// This effectively allows to inspect query information, before passing
113    /// request to schema for execution as long as query is valid.
114    pub fn parsed_query(&mut self) -> Result<&ExecutableDocument, ServerError> {
115        if self.parsed_query.is_none() {
116            match parse_query(&self.query) {
117                Ok(parsed) => self.parsed_query = Some(parsed),
118                Err(error) => return Err(error.into()),
119            }
120        }
121
122        // forbid_unsafe effectively bans optimize away else branch here so use unwrap
123        // but this unwrap never panics
124        Ok(self.parsed_query.as_ref().unwrap())
125    }
126
127    /// Sets the parsed query into the request.
128    ///
129    /// This is useful special with dynamic schema when the query has been
130    /// parsed ahead of time. It can reduce performance overhead of parsing
131    /// the query again.
132    pub fn set_parsed_query(&mut self, doc: ExecutableDocument) {
133        self.parsed_query = Some(doc);
134    }
135
136    /// Set a variable to an upload value.
137    ///
138    /// `var_path` is a dot-separated path to the item that begins with
139    /// `variables`, for example `variables.files.2.content` is equivalent
140    /// to the Rust code `request.variables["files"][2]["content"]`. If no
141    /// variable exists at the path this function won't do anything.
142    pub fn set_upload(&mut self, var_path: &str, upload: UploadValue) {
143        fn variable_path<'a>(variables: &'a mut Variables, path: &str) -> Option<&'a mut Value> {
144            let mut parts = path.strip_prefix("variables.")?.split('.');
145
146            let initial = variables.get_mut(parts.next().unwrap())?;
147
148            parts.try_fold(initial, |current, part| match current {
149                Value::List(list) => part
150                    .parse::<u32>()
151                    .ok()
152                    .and_then(|idx| usize::try_from(idx).ok())
153                    .and_then(move |idx| list.get_mut(idx)),
154                Value::Object(obj) => obj.get_mut(part),
155                _ => None,
156            })
157        }
158
159        let variable = match variable_path(&mut self.variables, var_path) {
160            Some(variable) => variable,
161            None => return,
162        };
163        self.uploads.push(upload);
164        *variable = Value::String(format!("#__graphql_file__:{}", self.uploads.len() - 1));
165    }
166}
167
168impl<T: Into<String>> From<T> for Request {
169    fn from(query: T) -> Self {
170        Self::new(query)
171    }
172}
173
174impl Debug for Request {
175    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
176        f.debug_struct("Request")
177            .field("query", &self.query)
178            .field("operation_name", &self.operation_name)
179            .field("variables", &self.variables)
180            .field("extensions", &self.extensions)
181            .finish()
182    }
183}
184
185/// Batch support for GraphQL requests, which is either a single query, or an
186/// array of queries
187///
188/// **Reference:** <https://www.apollographql.com/blog/batching-client-graphql-queries-a685f5bcd41b/>
189#[derive(Debug, Deserialize)]
190#[serde(untagged)]
191#[allow(clippy::large_enum_variant)] // Request is at fault
192pub enum BatchRequest {
193    /// Single query
194    Single(Request),
195
196    /// Non-empty array of queries
197    #[serde(deserialize_with = "deserialize_non_empty_vec")]
198    Batch(Vec<Request>),
199}
200
201impl BatchRequest {
202    /// Attempt to convert the batch request into a single request.
203    ///
204    /// # Errors
205    ///
206    /// Fails if the batch request is a list of requests with a message saying
207    /// that batch requests aren't supported.
208    pub fn into_single(self) -> Result<Request, ParseRequestError> {
209        match self {
210            Self::Single(req) => Ok(req),
211            Self::Batch(_) => Err(ParseRequestError::UnsupportedBatch),
212        }
213    }
214
215    /// Returns an iterator over the requests.
216    pub fn iter(&self) -> impl Iterator<Item = &Request> {
217        match self {
218            BatchRequest::Single(request) => {
219                Box::new(std::iter::once(request)) as Box<dyn Iterator<Item = &Request>>
220            }
221            BatchRequest::Batch(requests) => Box::new(requests.iter()),
222        }
223    }
224
225    /// Returns an iterator that allows modifying each request.
226    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Request> {
227        match self {
228            BatchRequest::Single(request) => {
229                Box::new(std::iter::once(request)) as Box<dyn Iterator<Item = &mut Request>>
230            }
231            BatchRequest::Batch(requests) => Box::new(requests.iter_mut()),
232        }
233    }
234
235    /// Specify the variables for each requests.
236    #[must_use]
237    pub fn variables(mut self, variables: Variables) -> Self {
238        for request in self.iter_mut() {
239            request.variables = variables.clone();
240        }
241        self
242    }
243
244    /// Insert some data for  for each requests.
245    #[must_use]
246    pub fn data<D: Any + Clone + Send + Sync>(mut self, data: D) -> Self {
247        for request in self.iter_mut() {
248            request.data.insert(data.clone());
249        }
250        self
251    }
252
253    /// Disable introspection queries for each request.
254    #[must_use]
255    pub fn disable_introspection(mut self) -> Self {
256        for request in self.iter_mut() {
257            request.introspection_mode = IntrospectionMode::Disabled;
258        }
259        self
260    }
261
262    /// Only allow introspection queries for each request.
263    #[must_use]
264    pub fn introspection_only(mut self) -> Self {
265        for request in self.iter_mut() {
266            request.introspection_mode = IntrospectionMode::IntrospectionOnly;
267        }
268        self
269    }
270}
271
272fn deserialize_non_empty_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
273where
274    D: Deserializer<'de>,
275    T: Deserialize<'de>,
276{
277    use serde::de::Error as _;
278
279    let v = <Vec<T>>::deserialize(deserializer)?;
280    if v.is_empty() {
281        Err(D::Error::invalid_length(0, &"a non-empty sequence"))
282    } else {
283        Ok(v)
284    }
285}
286
287impl From<Request> for BatchRequest {
288    fn from(r: Request) -> Self {
289        BatchRequest::Single(r)
290    }
291}
292
293impl From<Vec<Request>> for BatchRequest {
294    fn from(r: Vec<Request>) -> Self {
295        BatchRequest::Batch(r)
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use crate::*;
302
303    #[test]
304    fn test_request() {
305        let request: Request = from_value(value! ({
306            "query": "{ a b c }"
307        }))
308        .unwrap();
309        assert!(request.variables.is_empty());
310        assert!(request.operation_name.is_none());
311        assert_eq!(request.query, "{ a b c }");
312    }
313
314    #[test]
315    fn test_request_with_operation_name() {
316        let request: Request = from_value(value! ({
317            "query": "{ a b c }",
318            "operationName": "a"
319        }))
320        .unwrap();
321        assert!(request.variables.is_empty());
322        assert_eq!(request.operation_name.as_deref(), Some("a"));
323        assert_eq!(request.query, "{ a b c }");
324    }
325
326    #[test]
327    fn test_request_with_variables() {
328        let request: Request = from_value(value! ({
329            "query": "{ a b c }",
330            "variables": {
331                "v1": 100,
332                "v2": [1, 2, 3],
333                "v3": "str",
334            }
335        }))
336        .unwrap();
337        assert_eq!(
338            request.variables.into_value(),
339            value!({
340                "v1": 100,
341                "v2": [1, 2, 3],
342                "v3": "str",
343            })
344        );
345        assert!(request.operation_name.is_none());
346        assert_eq!(request.query, "{ a b c }");
347    }
348
349    #[test]
350    fn test_deserialize_request_with_empty_object_variables() {
351        let request: Request = from_value(value! ({
352            "query": "{ a b c }",
353            "variables": {}
354        }))
355        .unwrap();
356        assert!(request.operation_name.is_none());
357        assert!(request.variables.is_empty());
358    }
359
360    #[test]
361    fn test_deserialize_request_with_empty_array_variables() {
362        let error: DeserializerError = from_value::<Request>(value! ({
363            "query": "{ a b c }",
364            "variables": []
365        }))
366        .unwrap_err();
367        assert_eq!(error.to_string(), "invalid type: sequence, expected a map");
368    }
369
370    #[test]
371    fn test_deserialize_request_with_null_variables() {
372        let request: Request = from_value(value! ({
373            "query": "{ a b c }",
374            "variables": null
375        }))
376        .unwrap();
377        assert!(request.operation_name.is_none());
378        assert!(request.variables.is_empty());
379    }
380
381    #[test]
382    fn test_batch_request_single() {
383        let request: BatchRequest = from_value(value! ({
384            "query": "{ a b c }"
385        }))
386        .unwrap();
387
388        if let BatchRequest::Single(request) = request {
389            assert!(request.variables.is_empty());
390            assert!(request.operation_name.is_none());
391            assert_eq!(request.query, "{ a b c }");
392        } else {
393            unreachable!()
394        }
395    }
396
397    #[test]
398    fn test_batch_request_batch() {
399        let request: BatchRequest = from_value(value!([
400            {
401                "query": "{ a b c }"
402            },
403            {
404                "query": "{ d e }"
405            }
406        ]))
407        .unwrap();
408
409        if let BatchRequest::Batch(requests) = request {
410            assert!(requests[0].variables.is_empty());
411            assert!(requests[0].operation_name.is_none());
412            assert_eq!(requests[0].query, "{ a b c }");
413
414            assert!(requests[1].variables.is_empty());
415            assert!(requests[1].operation_name.is_none());
416            assert_eq!(requests[1].query, "{ d e }");
417        } else {
418            unreachable!()
419        }
420    }
421}