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_urlencoded_query(
170 url_encoded_query: String,
171 ) -> Result<Vec<Request>, serde_json::Error> {
172 let value: Value = serde_urlencoded::from_bytes(url_encoded_query.as_bytes())
173 .map_err(serde_json::Error::custom)?;
174
175 Request::process_query_values(value)
176 }
177
178 pub(crate) fn batch_from_bytes(bytes: &[u8]) -> Result<Vec<Request>, serde_json::Error> {
183 let value: Value = serde_json::from_slice(bytes).map_err(serde_json::Error::custom)?;
184
185 Request::process_batch_values(value)
186 }
187
188 fn allocate_result_array(value: &Value) -> Vec<Request> {
189 match value.as_array() {
190 Some(array) => Vec::with_capacity(array.len()),
191 None => Vec::with_capacity(1),
192 }
193 }
194
195 fn process_batch_values(value: Value) -> Result<Vec<Request>, serde_json::Error> {
196 let mut result = Request::allocate_result_array(&value);
197
198 if let Value::Array(entries) = value {
199 u64_histogram!(
200 "apollo.router.operations.batching.size",
201 "Number of queries contained within each query batch",
202 entries.len() as u64,
203 mode = BatchingMode::BatchHttpLink.to_string() );
205
206 u64_counter!(
207 "apollo.router.operations.batching",
208 "Total requests with batched operations",
209 1,
210 mode = BatchingMode::BatchHttpLink.to_string() );
212 for entry in entries {
213 let bytes = serde_json::to_vec(&entry)?;
214 result.push(Request::deserialize_from_bytes(&bytes.into())?);
215 }
216 } else {
217 let bytes = serde_json::to_vec(&value)?;
218 result.push(Request::deserialize_from_bytes(&bytes.into())?);
219 }
220 Ok(result)
221 }
222
223 fn process_query_values(value: Value) -> Result<Vec<Request>, serde_json::Error> {
224 let mut result = Request::allocate_result_array(&value);
225
226 if let Value::Array(entries) = value {
227 u64_histogram!(
228 "apollo.router.operations.batching.size",
229 "Number of queries contained within each query batch",
230 entries.len() as u64,
231 mode = BatchingMode::BatchHttpLink.to_string() );
233
234 u64_counter!(
235 "apollo.router.operations.batching",
236 "Total requests with batched operations",
237 1,
238 mode = BatchingMode::BatchHttpLink.to_string() );
240 for entry in entries {
241 result.push(Request::process_value(&entry)?);
242 }
243 } else {
244 result.push(Request::process_value(&value)?)
245 }
246 Ok(result)
247 }
248
249 fn process_value(value: &Value) -> Result<Request, serde_json::Error> {
250 let operation_name = value.get("operationName").and_then(Value::as_str);
251 let query = value.get("query").and_then(Value::as_str).map(String::from);
252 let variables: Object = value
253 .get("variables")
254 .and_then(Value::as_str)
255 .map(serde_json::from_str)
256 .transpose()?
257 .unwrap_or_default();
258 let extensions: Object = value
259 .get("extensions")
260 .and_then(Value::as_str)
261 .map(serde_json::from_str)
262 .transpose()?
263 .unwrap_or_default();
264
265 let request = Self::builder()
266 .and_query(query)
267 .variables(variables)
268 .and_operation_name(operation_name)
269 .extensions(extensions)
270 .build();
271 Ok(request)
272 }
273
274 pub fn from_urlencoded_query(url_encoded_query: String) -> Result<Request, serde_json::Error> {
280 let urldecoded: Value = serde_urlencoded::from_bytes(url_encoded_query.as_bytes())
281 .map_err(serde_json::Error::custom)?;
282
283 Request::process_value(&urldecoded)
284 }
285}
286
287struct RequestFromBytesSeed<'data>(&'data Bytes);
288
289impl<'de> DeserializeSeed<'de> for RequestFromBytesSeed<'_> {
290 type Value = Request;
291
292 fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
293 where
294 D: serde::Deserializer<'de>,
295 {
296 #[derive(serde::Deserialize)]
297 #[serde(field_identifier, rename_all = "camelCase")]
298 enum Field {
299 Query,
300 OperationName,
301 Variables,
302 Extensions,
303 #[serde(other)]
304 Other,
305 }
306
307 const FIELDS: &[&str] = &["query", "operationName", "variables", "extensions"];
308
309 struct RequestVisitor<'data>(&'data Bytes);
310
311 impl<'de> serde::de::Visitor<'de> for RequestVisitor<'_> {
312 type Value = Request;
313
314 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
315 formatter.write_str("a GraphQL request")
316 }
317
318 fn visit_map<V>(self, mut map: V) -> Result<Request, V::Error>
319 where
320 V: serde::de::MapAccess<'de>,
321 {
322 let mut query = None;
323 let mut operation_name = None;
324 let mut variables = None;
325 let mut extensions = None;
326 while let Some(key) = map.next_key()? {
327 match key {
328 Field::Query => {
329 if query.is_some() {
330 return Err(Error::duplicate_field("query"));
331 }
332 query = Some(map.next_value()?);
333 }
334 Field::OperationName => {
335 if operation_name.is_some() {
336 return Err(Error::duplicate_field("operationName"));
337 }
338 operation_name = Some(map.next_value()?);
339 }
340 Field::Variables => {
341 if variables.is_some() {
342 return Err(Error::duplicate_field("variables"));
343 }
344 let seed = serde_json_bytes::value::BytesSeed::new(self.0);
345 let value = map.next_value_seed(seed)?;
346 variables = Some(as_optional_object(value)?);
347 }
348 Field::Extensions => {
349 if extensions.is_some() {
350 return Err(Error::duplicate_field("extensions"));
351 }
352 let seed = serde_json_bytes::value::BytesSeed::new(self.0);
353 let value = map.next_value_seed(seed)?;
354 extensions = Some(as_optional_object(value)?);
355 }
356 Field::Other => {
357 let _: serde::de::IgnoredAny = map.next_value()?;
358 }
359 }
360 }
361 Ok(Request {
362 query: query.unwrap_or_default(),
363 operation_name: operation_name.unwrap_or_default(),
364 variables: variables.unwrap_or_default(),
365 extensions: extensions.unwrap_or_default(),
366 })
367 }
368 }
369
370 deserializer.deserialize_struct("Request", FIELDS, RequestVisitor(self.0))
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use serde_json::json;
377 use serde_json_bytes::json as bjson;
378 use test_log::test;
379
380 use super::*;
381
382 #[test]
383 fn test_request() {
384 let data = json!(
385 {
386 "query": "query aTest($arg1: String!) { test(who: $arg1) }",
387 "operationName": "aTest",
388 "variables": { "arg1": "me" },
389 "extensions": {"extension": 1}
390 });
391 let result = check_deserialization(data);
392 assert_eq!(
393 result,
394 Request::builder()
395 .query("query aTest($arg1: String!) { test(who: $arg1) }".to_owned())
396 .operation_name("aTest")
397 .variables(bjson!({ "arg1": "me" }).as_object().unwrap().clone())
398 .extensions(bjson!({"extension": 1}).as_object().cloned().unwrap())
399 .build()
400 );
401 }
402
403 #[test]
404 fn test_no_variables() {
405 let result = check_deserialization(json!(
406 {
407 "query": "query aTest($arg1: String!) { test(who: $arg1) }",
408 "operationName": "aTest",
409 "extensions": {"extension": 1}
410 }));
411 assert_eq!(
412 result,
413 Request::builder()
414 .query("query aTest($arg1: String!) { test(who: $arg1) }".to_owned())
415 .operation_name("aTest")
416 .extensions(bjson!({"extension": 1}).as_object().cloned().unwrap())
417 .build()
418 );
419 }
420
421 #[test]
422 fn test_variables_is_null() {
425 let result = check_deserialization(json!(
426 {
427 "query": "query aTest($arg1: String!) { test(who: $arg1) }",
428 "operationName": "aTest",
429 "variables": null,
430 "extensions": {"extension": 1}
431 }));
432 assert_eq!(
433 result,
434 Request::builder()
435 .query("query aTest($arg1: String!) { test(who: $arg1) }")
436 .operation_name("aTest")
437 .extensions(bjson!({"extension": 1}).as_object().cloned().unwrap())
438 .build()
439 );
440 }
441
442 #[test]
443 fn from_urlencoded_query_works() {
444 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();
445
446 let expected_result = check_deserialization(json!(
447 {
448 "query": "{ topProducts { upc name reviews { id product { name } author { id name } } } }",
449 "extensions": {
450 "persistedQuery": {
451 "version": 1,
452 "sha256Hash": "20a101de18d4a9331bfc4ccdfef33cc735876a689490433570f17bdd4c0bad3f"
453 }
454 }
455 }));
456
457 let req = Request::from_urlencoded_query(query_string).unwrap();
458
459 assert_eq!(expected_result, req);
460 }
461
462 #[test]
463 fn from_urlencoded_query_with_variables_works() {
464 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();
465
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 "persistedQuery": {
472 "version": 1,
473 "sha256Hash": "20a101de18d4a9331bfc4ccdfef33cc735876a689490433570f17bdd4c0bad3f"
474 }
475 }
476 }));
477
478 let req = Request::from_urlencoded_query(query_string).unwrap();
479
480 assert_eq!(expected_result, req);
481 }
482
483 #[test]
484 fn null_extensions() {
485 let expected_result = check_deserialization(json!(
486 {
487 "query": "{ topProducts { upc name reviews { id product { name } author { id name } } } }",
488 "variables": {"date": "2022-01-01T00:00:00+00:00"},
489 "extensions": null
490 }));
491 insta::assert_yaml_snapshot!(expected_result);
492 }
493
494 #[test]
495 fn missing_extensions() {
496 let expected_result = check_deserialization(json!(
497 {
498 "query": "{ topProducts { upc name reviews { id product { name } author { id name } } } }",
499 "variables": {"date": "2022-01-01T00:00:00+00:00"},
500 }));
501 insta::assert_yaml_snapshot!(expected_result);
502 }
503
504 #[test]
505 fn extensions() {
506 let expected_result = check_deserialization(json!(
507 {
508 "query": "{ topProducts { upc name reviews { id product { name } author { id name } } } }",
509 "variables": {"date": "2022-01-01T00:00:00+00:00"},
510 "extensions": {
511 "something_simple": "else",
512 "something_complex": {
513 "nested": "value"
514 }
515 }
516 }));
517 insta::assert_yaml_snapshot!(expected_result);
518 }
519
520 fn check_deserialization(request: serde_json::Value) -> Request {
521 let string = serde_json::to_string(&request).expect("could not serialize request");
524 let string_deserialized =
525 serde_json::from_str(&string).expect("could not deserialize string");
526 let bytes = Bytes::copy_from_slice(string.as_bytes());
527 let bytes_deserialized =
528 Request::deserialize_from_bytes(&bytes).expect("could not deserialize from bytes");
529 assert_eq!(
530 string_deserialized, bytes_deserialized,
531 "string and bytes deserialization did not match"
532 );
533 string_deserialized
534 }
535}