1use bytes::Bytes;
2use derivative::Derivative;
3use serde::Deserialize;
4use serde::Serialize;
5use serde::de::DeserializeSeed;
6use serde::de::Error;
7use serde_json_bytes::ByteString;
8use serde_json_bytes::Map as JsonMap;
9
10use crate::configuration::BatchingMode;
11use crate::json_ext::Object;
12use crate::json_ext::Value;
13
14#[derive(Clone, Derivative, Serialize, Deserialize, Default)]
16#[serde(rename_all = "camelCase")]
19#[derivative(Debug, PartialEq, Eq, Hash)]
20#[non_exhaustive]
21pub struct Request {
22 #[serde(skip_serializing_if = "Option::is_none", default)]
27 pub query: Option<String>,
28
29 #[serde(skip_serializing_if = "Option::is_none", default)]
36 pub operation_name: Option<String>,
37
38 #[serde(
44 skip_serializing_if = "Object::is_empty",
45 default,
46 deserialize_with = "deserialize_null_default"
47 )]
48 pub variables: Object,
49
50 #[serde(
74 skip_serializing_if = "Object::is_empty",
75 default,
76 deserialize_with = "deserialize_null_default"
77 )]
78 pub extensions: Object,
79}
80
81fn deserialize_null_default<'de, D, T: Default + Deserialize<'de>>(
83 deserializer: D,
84) -> Result<T, D::Error>
85where
86 D: serde::Deserializer<'de>,
87{
88 <Option<T>>::deserialize(deserializer).map(|x| x.unwrap_or_default())
89}
90
91fn as_optional_object<E: Error>(value: Value) -> Result<Object, E> {
92 use serde::de::Unexpected;
93
94 let exp = "a map or null";
95 match value {
96 Value::Object(object) => Ok(object),
97 Value::Null => Ok(Object::default()),
99 Value::Bool(value) => Err(E::invalid_type(Unexpected::Bool(value), &exp)),
100 Value::Number(_) => Err(E::invalid_type(Unexpected::Other("a number"), &exp)),
101 Value::String(value) => Err(E::invalid_type(Unexpected::Str(value.as_str()), &exp)),
102 Value::Array(_) => Err(E::invalid_type(Unexpected::Seq, &exp)),
103 }
104}
105
106#[buildstructor::buildstructor]
107impl Request {
108 #[builder(visibility = "pub")]
109 fn new(
118 query: Option<String>,
119 operation_name: Option<String>,
120 variables: JsonMap<ByteString, Value>,
122 extensions: JsonMap<ByteString, Value>,
123 ) -> Self {
124 Self {
125 query,
126 operation_name,
127 variables,
128 extensions,
129 }
130 }
131
132 #[builder(visibility = "pub")]
133 fn fake_new(
143 query: Option<String>,
144 operation_name: Option<String>,
145 variables: JsonMap<ByteString, Value>,
147 extensions: JsonMap<ByteString, Value>,
148 ) -> Self {
149 Self {
150 query,
151 operation_name,
152 variables,
153 extensions,
154 }
155 }
156
157 pub fn deserialize_from_bytes(data: &Bytes) -> Result<Self, serde_json::Error> {
159 let seed = RequestFromBytesSeed(data);
160 let mut de = serde_json::Deserializer::from_slice(data);
161 seed.deserialize(&mut de)
162 }
163
164 pub(crate) fn batch_from_bytes(bytes: &[u8]) -> Result<Vec<Request>, serde_json::Error> {
169 let value: Value = serde_json::from_slice(bytes).map_err(serde_json::Error::custom)?;
170
171 Request::process_batch_values(value)
172 }
173
174 fn allocate_result_array(value: &Value) -> Vec<Request> {
175 match value.as_array() {
176 Some(array) => Vec::with_capacity(array.len()),
177 None => Vec::with_capacity(1),
178 }
179 }
180
181 fn process_batch_values(value: Value) -> Result<Vec<Request>, serde_json::Error> {
182 let mut result = Request::allocate_result_array(&value);
183
184 if let Value::Array(entries) = value {
185 u64_histogram!(
186 "apollo.router.operations.batching.size",
187 "Number of queries contained within each query batch",
188 entries.len() as u64,
189 mode = BatchingMode::BatchHttpLink.to_string() );
191
192 u64_counter!(
193 "apollo.router.operations.batching",
194 "Total requests with batched operations",
195 1,
196 mode = BatchingMode::BatchHttpLink.to_string() );
198 for entry in entries {
199 let bytes = serde_json::to_vec(&entry)?;
200 result.push(Request::deserialize_from_bytes(&bytes.into())?);
201 }
202 } else {
203 let bytes = serde_json::to_vec(&value)?;
204 result.push(Request::deserialize_from_bytes(&bytes.into())?);
205 }
206 Ok(result)
207 }
208
209 fn process_value(value: &Value) -> Result<Request, serde_json::Error> {
210 let operation_name = value.get("operationName").and_then(Value::as_str);
211 let query = value.get("query").and_then(Value::as_str).map(String::from);
212 let variables: Object = value
213 .get("variables")
214 .and_then(Value::as_str)
215 .map(serde_json::from_str)
216 .transpose()?
217 .unwrap_or_default();
218 let extensions: Object = value
219 .get("extensions")
220 .and_then(Value::as_str)
221 .map(serde_json::from_str)
222 .transpose()?
223 .unwrap_or_default();
224
225 let request = Self::builder()
226 .and_query(query)
227 .variables(variables)
228 .and_operation_name(operation_name)
229 .extensions(extensions)
230 .build();
231 Ok(request)
232 }
233
234 pub fn from_urlencoded_query(url_encoded_query: String) -> Result<Request, serde_json::Error> {
240 let urldecoded: Value = serde_urlencoded::from_bytes(url_encoded_query.as_bytes())
241 .map_err(serde_json::Error::custom)?;
242
243 Request::process_value(&urldecoded)
244 }
245}
246
247struct RequestFromBytesSeed<'data>(&'data Bytes);
248
249impl<'de> DeserializeSeed<'de> for RequestFromBytesSeed<'_> {
250 type Value = Request;
251
252 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
253 where
254 D: serde::Deserializer<'de>,
255 {
256 #[derive(serde::Deserialize)]
257 #[serde(field_identifier, rename_all = "camelCase")]
258 enum Field {
259 Query,
260 OperationName,
261 Variables,
262 Extensions,
263 #[serde(other)]
264 Other,
265 }
266
267 const FIELDS: &[&str] = &["query", "operationName", "variables", "extensions"];
268
269 struct RequestVisitor<'data>(&'data Bytes);
270
271 impl<'de> serde::de::Visitor<'de> for RequestVisitor<'_> {
272 type Value = Request;
273
274 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
275 formatter.write_str("a GraphQL request")
276 }
277
278 fn visit_map<V>(self, mut map: V) -> Result<Request, V::Error>
279 where
280 V: serde::de::MapAccess<'de>,
281 {
282 let mut query = None;
283 let mut operation_name = None;
284 let mut variables = None;
285 let mut extensions = None;
286 while let Some(key) = map.next_key()? {
287 match key {
288 Field::Query => {
289 if query.is_some() {
290 return Err(Error::duplicate_field("query"));
291 }
292 query = Some(map.next_value()?);
293 }
294 Field::OperationName => {
295 if operation_name.is_some() {
296 return Err(Error::duplicate_field("operationName"));
297 }
298 operation_name = Some(map.next_value()?);
299 }
300 Field::Variables => {
301 if variables.is_some() {
302 return Err(Error::duplicate_field("variables"));
303 }
304 let seed = serde_json_bytes::value::BytesSeed::new(self.0);
305 let value = map.next_value_seed(seed)?;
306 variables = Some(as_optional_object(value)?);
307 }
308 Field::Extensions => {
309 if extensions.is_some() {
310 return Err(Error::duplicate_field("extensions"));
311 }
312 let seed = serde_json_bytes::value::BytesSeed::new(self.0);
313 let value = map.next_value_seed(seed)?;
314 extensions = Some(as_optional_object(value)?);
315 }
316 Field::Other => {
317 let _: serde::de::IgnoredAny = map.next_value()?;
318 }
319 }
320 }
321 Ok(Request {
322 query: query.unwrap_or_default(),
323 operation_name: operation_name.unwrap_or_default(),
324 variables: variables.unwrap_or_default(),
325 extensions: extensions.unwrap_or_default(),
326 })
327 }
328 }
329
330 deserializer.deserialize_struct("Request", FIELDS, RequestVisitor(self.0))
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use serde_json::json;
337 use serde_json_bytes::json as bjson;
338 use test_log::test;
339
340 use super::*;
341
342 #[test]
343 fn test_request() {
344 let data = json!(
345 {
346 "query": "query aTest($arg1: String!) { test(who: $arg1) }",
347 "operationName": "aTest",
348 "variables": { "arg1": "me" },
349 "extensions": {"extension": 1}
350 });
351 let result = check_deserialization(data);
352 assert_eq!(
353 result,
354 Request::builder()
355 .query("query aTest($arg1: String!) { test(who: $arg1) }".to_owned())
356 .operation_name("aTest")
357 .variables(bjson!({ "arg1": "me" }).as_object().unwrap().clone())
358 .extensions(bjson!({"extension": 1}).as_object().cloned().unwrap())
359 .build()
360 );
361 }
362
363 #[test]
364 fn test_no_variables() {
365 let result = check_deserialization(json!(
366 {
367 "query": "query aTest($arg1: String!) { test(who: $arg1) }",
368 "operationName": "aTest",
369 "extensions": {"extension": 1}
370 }));
371 assert_eq!(
372 result,
373 Request::builder()
374 .query("query aTest($arg1: String!) { test(who: $arg1) }".to_owned())
375 .operation_name("aTest")
376 .extensions(bjson!({"extension": 1}).as_object().cloned().unwrap())
377 .build()
378 );
379 }
380
381 #[test]
382 fn test_variables_is_null() {
385 let result = check_deserialization(json!(
386 {
387 "query": "query aTest($arg1: String!) { test(who: $arg1) }",
388 "operationName": "aTest",
389 "variables": null,
390 "extensions": {"extension": 1}
391 }));
392 assert_eq!(
393 result,
394 Request::builder()
395 .query("query aTest($arg1: String!) { test(who: $arg1) }")
396 .operation_name("aTest")
397 .extensions(bjson!({"extension": 1}).as_object().cloned().unwrap())
398 .build()
399 );
400 }
401
402 #[test]
403 fn from_urlencoded_query_works() {
404 let query_string = "query=%7B+topProducts+%7B+upc+name+reviews+%7B+id+product+%7B+name+%7D+author+%7B+id+name+%7D+%7D+%7D+%7D&extensions=%7B+%22persistedQuery%22+%3A+%7B+%22version%22+%3A+1%2C+%22sha256Hash%22+%3A+%2220a101de18d4a9331bfc4ccdfef33cc735876a689490433570f17bdd4c0bad3f%22+%7D+%7D".to_string();
405
406 let expected_result = check_deserialization(json!(
407 {
408 "query": "{ topProducts { upc name reviews { id product { name } author { id name } } } }",
409 "extensions": {
410 "persistedQuery": {
411 "version": 1,
412 "sha256Hash": "20a101de18d4a9331bfc4ccdfef33cc735876a689490433570f17bdd4c0bad3f"
413 }
414 }
415 }));
416
417 let req = Request::from_urlencoded_query(query_string).unwrap();
418
419 assert_eq!(expected_result, req);
420 }
421
422 #[test]
423 fn from_urlencoded_query_with_variables_works() {
424 let query_string = "query=%7B+topProducts+%7B+upc+name+reviews+%7B+id+product+%7B+name+%7D+author+%7B+id+name+%7D+%7D+%7D+%7D&variables=%7B%22date%22%3A%222022-01-01T00%3A00%3A00%2B00%3A00%22%7D&extensions=%7B+%22persistedQuery%22+%3A+%7B+%22version%22+%3A+1%2C+%22sha256Hash%22+%3A+%2220a101de18d4a9331bfc4ccdfef33cc735876a689490433570f17bdd4c0bad3f%22+%7D+%7D".to_string();
425
426 let expected_result = check_deserialization(json!(
427 {
428 "query": "{ topProducts { upc name reviews { id product { name } author { id name } } } }",
429 "variables": {"date": "2022-01-01T00:00:00+00:00"},
430 "extensions": {
431 "persistedQuery": {
432 "version": 1,
433 "sha256Hash": "20a101de18d4a9331bfc4ccdfef33cc735876a689490433570f17bdd4c0bad3f"
434 }
435 }
436 }));
437
438 let req = Request::from_urlencoded_query(query_string).unwrap();
439
440 assert_eq!(expected_result, req);
441 }
442
443 #[test]
444 fn null_extensions() {
445 let expected_result = check_deserialization(json!(
446 {
447 "query": "{ topProducts { upc name reviews { id product { name } author { id name } } } }",
448 "variables": {"date": "2022-01-01T00:00:00+00:00"},
449 "extensions": null
450 }));
451 insta::assert_yaml_snapshot!(expected_result);
452 }
453
454 #[test]
455 fn missing_extensions() {
456 let expected_result = check_deserialization(json!(
457 {
458 "query": "{ topProducts { upc name reviews { id product { name } author { id name } } } }",
459 "variables": {"date": "2022-01-01T00:00:00+00:00"},
460 }));
461 insta::assert_yaml_snapshot!(expected_result);
462 }
463
464 #[test]
465 fn extensions() {
466 let expected_result = check_deserialization(json!(
467 {
468 "query": "{ topProducts { upc name reviews { id product { name } author { id name } } } }",
469 "variables": {"date": "2022-01-01T00:00:00+00:00"},
470 "extensions": {
471 "something_simple": "else",
472 "something_complex": {
473 "nested": "value"
474 }
475 }
476 }));
477 insta::assert_yaml_snapshot!(expected_result);
478 }
479
480 fn check_deserialization(request: serde_json::Value) -> Request {
481 let string = serde_json::to_string(&request).expect("could not serialize request");
484 let string_deserialized =
485 serde_json::from_str(&string).expect("could not deserialize string");
486 let bytes = Bytes::copy_from_slice(string.as_bytes());
487 let bytes_deserialized =
488 Request::deserialize_from_bytes(&bytes).expect("could not deserialize from bytes");
489 assert_eq!(
490 string_deserialized, bytes_deserialized,
491 "string and bytes deserialization did not match"
492 );
493 string_deserialized
494 }
495}