Skip to main content

apollo_router/graphql/
response.rs

1#![allow(missing_docs)] // FIXME
2use std::time::Instant;
3
4use apollo_compiler::response::ExecutionResponse;
5use bytes::Bytes;
6use displaydoc::Display;
7use serde::Deserialize;
8use serde::Serialize;
9use serde_json_bytes::ByteString;
10use serde_json_bytes::Map;
11
12use crate::error::Error;
13use crate::graphql::IntoGraphQLErrors;
14use crate::json_ext::Object;
15use crate::json_ext::Path;
16use crate::json_ext::Value;
17
18#[derive(thiserror::Error, Display, Debug, Eq, PartialEq)]
19#[error("GraphQL response was malformed: {reason}")]
20pub(crate) struct MalformedResponseError {
21    /// The reason the deserialization failed.
22    pub(crate) reason: String,
23}
24
25/// A graphql primary response.
26/// Used for federated and subgraph queries.
27#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)]
28#[serde(rename_all = "camelCase")]
29#[non_exhaustive]
30pub struct Response {
31    /// The label that was passed to the defer or stream directive for this patch.
32    #[serde(skip_serializing_if = "Option::is_none", default)]
33    pub label: Option<String>,
34
35    /// The response data.
36    #[serde(skip_serializing_if = "Option::is_none", default)]
37    pub data: Option<Value>,
38
39    /// The path that the data should be merged at.
40    #[serde(skip_serializing_if = "Option::is_none", default)]
41    pub path: Option<Path>,
42
43    /// The optional graphql errors encountered.
44    #[serde(skip_serializing_if = "Vec::is_empty", default)]
45    pub errors: Vec<Error>,
46
47    /// The optional graphql extensions.
48    #[serde(skip_serializing_if = "Object::is_empty", default)]
49    pub extensions: Object,
50
51    #[serde(skip_serializing_if = "Option::is_none", default)]
52    pub has_next: Option<bool>,
53
54    #[serde(skip, default)]
55    pub subscribed: Option<bool>,
56
57    /// Used for subscription event to compute the duration of a subscription event
58    #[serde(skip, default)]
59    pub created_at: Option<Instant>,
60
61    #[serde(skip_serializing_if = "Vec::is_empty", default)]
62    pub incremental: Vec<IncrementalResponse>,
63}
64
65#[buildstructor::buildstructor]
66impl Response {
67    /// Constructor
68    #[builder(visibility = "pub")]
69    fn new(
70        label: Option<String>,
71        data: Option<Value>,
72        path: Option<Path>,
73        errors: Vec<Error>,
74        extensions: Map<ByteString, Value>,
75        _subselection: Option<String>,
76        has_next: Option<bool>,
77        subscribed: Option<bool>,
78        incremental: Vec<IncrementalResponse>,
79        created_at: Option<Instant>,
80    ) -> Self {
81        Self {
82            label,
83            data,
84            path,
85            errors,
86            extensions,
87            has_next,
88            subscribed,
89            incremental,
90            created_at,
91        }
92    }
93
94    /// If path is None, this is a primary response.
95    pub fn is_primary(&self) -> bool {
96        self.path.is_none()
97    }
98
99    /// append_errors default the errors `path` with the one provided.
100    pub fn append_errors(&mut self, errors: &mut Vec<Error>) {
101        self.errors.append(errors)
102    }
103
104    /// Create a [`Response`] from the supplied [`Bytes`].
105    ///
106    /// This will return an error (identifying the faulty service) if the input is invalid.
107    pub(crate) fn from_bytes(b: Bytes) -> Result<Response, MalformedResponseError> {
108        let value = Value::from_bytes(b).map_err(|error| MalformedResponseError {
109            reason: error.to_string(),
110        })?;
111        Response::from_value(value)
112    }
113
114    pub(crate) fn from_value(value: Value) -> Result<Response, MalformedResponseError> {
115        let mut object = ensure_object!(value).map_err(|error| MalformedResponseError {
116            reason: error.to_string(),
117        })?;
118        let data = object.remove("data");
119        let errors = extract_key_value_from_object!(object, "errors", Value::Array(v) => v)
120            .map_err(|err| MalformedResponseError {
121                reason: err.to_string(),
122            })?
123            .into_iter()
124            .flatten()
125            .map(Error::from_value)
126            .collect::<Result<Vec<Error>, MalformedResponseError>>()?;
127        let extensions =
128            extract_key_value_from_object!(object, "extensions", Value::Object(o) => o)
129                .map_err(|err| MalformedResponseError {
130                    reason: err.to_string(),
131                })?
132                .unwrap_or_default();
133        let label = extract_key_value_from_object!(object, "label", Value::String(s) => s)
134            .map_err(|err| MalformedResponseError {
135                reason: err.to_string(),
136            })?
137            .map(|s| s.as_str().to_string());
138        let path = extract_key_value_from_object!(object, "path")
139            .map(serde_json_bytes::from_value)
140            .transpose()
141            .map_err(|err| MalformedResponseError {
142                reason: err.to_string(),
143            })?;
144        let has_next = extract_key_value_from_object!(object, "hasNext", Value::Bool(b) => b)
145            .map_err(|err| MalformedResponseError {
146                reason: err.to_string(),
147            })?;
148        let incremental =
149            extract_key_value_from_object!(object, "incremental", Value::Array(a) => a).map_err(
150                |err| MalformedResponseError {
151                    reason: err.to_string(),
152                },
153            )?;
154        let incremental: Vec<IncrementalResponse> = match incremental {
155            Some(v) => v
156                .into_iter()
157                .map(serde_json_bytes::from_value)
158                .collect::<Result<Vec<IncrementalResponse>, _>>()
159                .map_err(|err| MalformedResponseError {
160                    reason: err.to_string(),
161                })?,
162            None => vec![],
163        };
164        // Graphql spec says:
165        // If the data entry in the response is not present, the errors entry in the response must not be empty.
166        // It must contain at least one error. The errors it contains should indicate why no data was able to be returned.
167        if data.is_none() && errors.is_empty() {
168            return Err(MalformedResponseError {
169                reason: "graphql response without data must contain at least one error".to_string(),
170            });
171        }
172
173        Ok(Response {
174            label,
175            data,
176            path,
177            errors,
178            extensions,
179            has_next,
180            subscribed: None,
181            incremental,
182            created_at: None,
183        })
184    }
185}
186
187/// A graphql incremental response.
188/// Used with `@defer`
189#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)]
190#[serde(rename_all = "camelCase")]
191#[non_exhaustive]
192pub struct IncrementalResponse {
193    /// The label that was passed to the defer or stream directive for this patch.
194    #[serde(skip_serializing_if = "Option::is_none", default)]
195    pub label: Option<String>,
196
197    /// The response data.
198    #[serde(skip_serializing_if = "Option::is_none", default)]
199    pub data: Option<Value>,
200
201    /// The path that the data should be merged at.
202    #[serde(skip_serializing_if = "Option::is_none", default)]
203    pub path: Option<Path>,
204
205    /// The optional graphql errors encountered.
206    #[serde(skip_serializing_if = "Vec::is_empty", default)]
207    pub errors: Vec<Error>,
208
209    /// The optional graphql extensions.
210    #[serde(skip_serializing_if = "Object::is_empty", default)]
211    pub extensions: Object,
212}
213
214#[buildstructor::buildstructor]
215impl IncrementalResponse {
216    /// Constructor
217    #[builder(visibility = "pub")]
218    fn new(
219        label: Option<String>,
220        data: Option<Value>,
221        path: Option<Path>,
222        errors: Vec<Error>,
223        extensions: Map<ByteString, Value>,
224    ) -> Self {
225        Self {
226            label,
227            data,
228            path,
229            errors,
230            extensions,
231        }
232    }
233
234    /// append_errors default the errors `path` with the one provided.
235    pub fn append_errors(&mut self, errors: &mut Vec<Error>) {
236        self.errors.append(errors)
237    }
238}
239
240impl From<ExecutionResponse> for Response {
241    fn from(response: ExecutionResponse) -> Response {
242        let ExecutionResponse { errors, data } = response;
243        Self {
244            errors: errors.into_graphql_errors().unwrap(),
245            data: data.map(serde_json_bytes::Value::Object),
246            extensions: Default::default(),
247            label: None,
248            path: None,
249            has_next: None,
250            subscribed: None,
251            created_at: None,
252            incremental: Vec::new(),
253        }
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use serde_json::json;
260    use serde_json_bytes::json as bjson;
261
262    use super::*;
263    use crate::graphql::Location;
264
265    #[test]
266    fn test_append_errors_path_fallback_and_override() {
267        let expected_errors = vec![
268            Error {
269                message: "Something terrible happened!".to_string(),
270                path: Some(Path::from("here")),
271                ..Default::default()
272            },
273            Error {
274                message: "I mean for real".to_string(),
275                ..Default::default()
276            },
277        ];
278
279        let mut errors_to_append = vec![
280            Error {
281                message: "Something terrible happened!".to_string(),
282                path: Some(Path::from("here")),
283                ..Default::default()
284            },
285            Error {
286                message: "I mean for real".to_string(),
287                ..Default::default()
288            },
289        ];
290
291        let mut response = Response::builder().build();
292        response.append_errors(&mut errors_to_append);
293        assert_eq!(response.errors, expected_errors);
294    }
295
296    #[test]
297    fn test_response() {
298        let result = serde_json::from_str::<Response>(
299            json!(
300            {
301              "errors": [
302                {
303                  "message": "Name for character with ID 1002 could not be fetched.",
304                  "locations": [{ "line": 6, "column": 7 }],
305                  "path": ["hero", "heroFriends", 1, "name"],
306                  "extensions": {
307                    "error-extension": 5,
308                  }
309                }
310              ],
311              "data": {
312                "hero": {
313                  "name": "R2-D2",
314                  "heroFriends": [
315                    {
316                      "id": "1000",
317                      "name": "Luke Skywalker"
318                    },
319                    {
320                      "id": "1002",
321                      "name": null
322                    },
323                    {
324                      "id": "1003",
325                      "name": "Leia Organa"
326                    }
327                  ]
328                }
329              },
330              "extensions": {
331                "response-extension": 3,
332              }
333            })
334            .to_string()
335            .as_str(),
336        );
337        assert_eq!(
338            result.unwrap(),
339            Response::builder()
340                .data(json!({
341                  "hero": {
342                    "name": "R2-D2",
343                    "heroFriends": [
344                      {
345                        "id": "1000",
346                        "name": "Luke Skywalker"
347                      },
348                      {
349                        "id": "1002",
350                        "name": null
351                      },
352                      {
353                        "id": "1003",
354                        "name": "Leia Organa"
355                      }
356                    ]
357                  }
358                }))
359                .errors(vec![Error {
360                    message: "Name for character with ID 1002 could not be fetched.".into(),
361                    locations: vec!(Location { line: 6, column: 7 }),
362                    path: Some(Path::from("hero/heroFriends/1/name")),
363                    extensions: bjson!({
364                        "error-extension": 5,
365                    })
366                    .as_object()
367                    .cloned()
368                    .unwrap()
369                }])
370                .extensions(
371                    bjson!({
372                        "response-extension": 3,
373                    })
374                    .as_object()
375                    .cloned()
376                    .unwrap()
377                )
378                .build()
379        );
380    }
381
382    #[test]
383    fn test_patch_response() {
384        let result = serde_json::from_str::<Response>(
385            json!(
386            {
387              "label": "part",
388              "hasNext": true,
389              "path": ["hero", "heroFriends", 1, "name"],
390              "errors": [
391                {
392                  "message": "Name for character with ID 1002 could not be fetched.",
393                  "locations": [{ "line": 6, "column": 7 }],
394                  "path": ["hero", "heroFriends", 1, "name"],
395                  "extensions": {
396                    "error-extension": 5,
397                  }
398                }
399              ],
400              "data": {
401                "hero": {
402                  "name": "R2-D2",
403                  "heroFriends": [
404                    {
405                      "id": "1000",
406                      "name": "Luke Skywalker"
407                    },
408                    {
409                      "id": "1002",
410                      "name": null
411                    },
412                    {
413                      "id": "1003",
414                      "name": "Leia Organa"
415                    }
416                  ]
417                }
418              },
419              "extensions": {
420                "response-extension": 3,
421              }
422            })
423            .to_string()
424            .as_str(),
425        );
426        assert_eq!(
427            result.unwrap(),
428            Response::builder()
429                .label("part".to_owned())
430                .data(json!({
431                  "hero": {
432                    "name": "R2-D2",
433                    "heroFriends": [
434                      {
435                        "id": "1000",
436                        "name": "Luke Skywalker"
437                      },
438                      {
439                        "id": "1002",
440                        "name": null
441                      },
442                      {
443                        "id": "1003",
444                        "name": "Leia Organa"
445                      }
446                    ]
447                  }
448                }))
449                .path(Path::from("hero/heroFriends/1/name"))
450                .errors(vec![Error {
451                    message: "Name for character with ID 1002 could not be fetched.".into(),
452                    locations: vec!(Location { line: 6, column: 7 }),
453                    path: Some(Path::from("hero/heroFriends/1/name")),
454                    extensions: bjson!({
455                        "error-extension": 5,
456                    })
457                    .as_object()
458                    .cloned()
459                    .unwrap()
460                }])
461                .extensions(
462                    bjson!({
463                        "response-extension": 3,
464                    })
465                    .as_object()
466                    .cloned()
467                    .unwrap()
468                )
469                .has_next(true)
470                .build()
471        );
472    }
473
474    #[test]
475    fn test_no_data_and_no_errors() {
476        let response = Response::from_bytes("{\"errors\":null}".into());
477        assert_eq!(
478            response.expect_err("no data and no errors"),
479            MalformedResponseError {
480                reason: "graphql response without data must contain at least one error".to_string(),
481            }
482        );
483    }
484
485    #[test]
486    fn test_data_null() {
487        let response = Response::from_bytes("{\"data\":null}".into()).unwrap();
488        assert_eq!(
489            response,
490            Response::builder().data(Some(Value::Null)).build(),
491        );
492    }
493}