apollo_router/graphql/
mod.rs

1//! Types related to GraphQL requests, responses, etc.
2
3mod request;
4mod response;
5mod visitor;
6
7use std::fmt;
8use std::pin::Pin;
9use std::str::FromStr;
10
11use apollo_compiler::response::GraphQLError as CompilerExecutionError;
12use apollo_compiler::response::ResponseDataPathSegment;
13use futures::Stream;
14use heck::ToShoutySnakeCase;
15pub use request::Request;
16pub use response::IncrementalResponse;
17use response::MalformedResponseError;
18pub use response::Response;
19use serde::Deserialize;
20use serde::Serialize;
21use serde_json_bytes::ByteString;
22use serde_json_bytes::Map as JsonMap;
23use serde_json_bytes::Value;
24use uuid::Uuid;
25pub(crate) use visitor::ResponseVisitor;
26
27use crate::json_ext::Object;
28use crate::json_ext::Path;
29pub use crate::json_ext::Path as JsonPath;
30pub use crate::json_ext::PathElement as JsonPathElement;
31use crate::spec::query::ERROR_CODE_RESPONSE_VALIDATION;
32
33/// An asynchronous [`Stream`] of GraphQL [`Response`]s.
34///
35/// In some cases such as with `@defer`, a single HTTP response from the Router
36/// may contain multiple GraphQL responses that will be sent at different times
37/// (as more data becomes available).
38///
39/// We represent this in Rust as a stream,
40/// even if that stream happens to only contain one item.
41pub type ResponseStream = Pin<Box<dyn Stream<Item = Response> + Send>>;
42
43#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
44#[serde(rename_all = "camelCase")]
45/// The error location
46pub struct Location {
47    /// The line number
48    pub line: u32,
49    /// The column number
50    pub column: u32,
51}
52
53/// A [GraphQL error](https://spec.graphql.org/October2021/#sec-Errors)
54/// as may be found in the `errors` field of a GraphQL [`Response`].
55///
56/// Converted to (or from) JSON with serde.
57#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
58#[serde(rename_all = "camelCase", default)]
59#[non_exhaustive]
60pub struct Error {
61    /// The error message.
62    pub message: String,
63
64    /// The locations of the error in the GraphQL document of the originating request.
65    #[serde(skip_serializing_if = "Vec::is_empty")]
66    pub locations: Vec<Location>,
67
68    /// If this is a field error, the JSON path to that field in [`Response::data`]
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub path: Option<Path>,
71
72    /// The optional GraphQL extensions for this error.
73    #[serde(skip_serializing_if = "Object::is_empty")]
74    pub extensions: Object,
75
76    /// A unique identifier for this error
77    #[serde(skip_serializing)]
78    apollo_id: Uuid,
79}
80
81impl Default for Error {
82    fn default() -> Self {
83        Self {
84            message: String::new(),
85            locations: Vec::new(),
86            path: None,
87            extensions: Object::new(),
88            apollo_id: generate_uuid(),
89        }
90    }
91}
92
93// Implement getter and getter_mut to not use pub field directly
94
95#[buildstructor::buildstructor]
96impl Error {
97    /// Returns a builder that builds a GraphQL [`Error`] from its components.
98    ///
99    /// Builder methods:
100    ///
101    /// * `.message(impl Into<`[`String`]`>)`
102    ///   Required.
103    ///   Sets [`Error::message`].
104    ///
105    /// * `.locations(impl Into<`[`Vec`]`<`[`Location`]`>>)`
106    ///   Optional.
107    ///   Sets the entire `Vec` of [`Error::locations`], which defaults to the empty.
108    ///
109    /// * `.location(impl Into<`[`Location`]`>)`
110    ///   Optional, may be called multiple times.
111    ///   Adds one item at the end of [`Error::locations`].
112    ///
113    /// * `.path(impl Into<`[`Path`]`>)`
114    ///   Optional.
115    ///   Sets [`Error::path`].
116    ///
117    /// * `.extensions(impl Into<`[`serde_json_bytes::Map`]`<`[`ByteString`]`, `[`Value`]`>>)`
118    ///   Optional.
119    ///   Sets the entire [`Error::extensions`] map, which defaults to empty.
120    ///
121    /// * `.extension(impl Into<`[`ByteString`]`>, impl Into<`[`Value`]`>)`
122    ///   Optional, may be called multiple times.
123    ///   Adds one item to the [`Error::extensions`] map.
124    ///
125    /// * `.extension_code(impl Into<`[`String`]`>)`
126    ///   Optional.
127    ///   Sets the "code" in the extension map. Will be ignored if extension already has this key
128    ///   set.
129    ///
130    /// * `.apollo_id(impl Into<`[`Uuid`]`>)`
131    ///   Optional.
132    ///   Sets the unique identifier for this Error. This should only be used in cases of
133    ///   deserialization or testing. If not given, the ID will be auto-generated.
134    ///
135    /// * `.build()`
136    ///   Finishes the builder and returns a GraphQL [`Error`].
137    #[builder(visibility = "pub")]
138    fn new(
139        message: String,
140        locations: Vec<Location>,
141        path: Option<Path>,
142        extension_code: Option<String>,
143        // Skip the `Object` type alias in order to use buildstructor's map special-casing
144        mut extensions: JsonMap<ByteString, Value>,
145        apollo_id: Option<Uuid>,
146    ) -> Self {
147        if let Some(code) = extension_code {
148            extensions
149                .entry("code")
150                .or_insert(Value::String(ByteString::from(code)));
151        }
152        Self {
153            message,
154            locations,
155            path,
156            extensions,
157            apollo_id: apollo_id.unwrap_or_else(Uuid::new_v4),
158        }
159    }
160
161    pub(crate) fn from_value(value: Value) -> Result<Error, MalformedResponseError> {
162        let mut object = ensure_object!(value).map_err(|error| MalformedResponseError {
163            reason: format!("invalid error within `errors`: {}", error),
164        })?;
165
166        let extensions =
167            extract_key_value_from_object!(object, "extensions", Value::Object(o) => o)
168                .map_err(|err| MalformedResponseError {
169                    reason: format!("invalid `extensions` within error: {}", err),
170                })?
171                .unwrap_or_default();
172        let message = match extract_key_value_from_object!(object, "message", Value::String(s) => s)
173        {
174            Ok(Some(s)) => Ok(s.as_str().to_string()),
175            Ok(None) => Err(MalformedResponseError {
176                reason: "missing required `message` property within error".to_owned(),
177            }),
178            Err(err) => Err(MalformedResponseError {
179                reason: format!("invalid `message` within error: {}", err),
180            }),
181        }?;
182        let locations = extract_key_value_from_object!(object, "locations")
183            .map(skip_invalid_locations)
184            .map(serde_json_bytes::from_value)
185            .transpose()
186            .map_err(|err| MalformedResponseError {
187                reason: format!("invalid `locations` within error: {}", err),
188            })?
189            .unwrap_or_default();
190        let path = extract_key_value_from_object!(object, "path")
191            .map(serde_json_bytes::from_value)
192            .transpose()
193            .map_err(|err| MalformedResponseError {
194                reason: format!("invalid `path` within error: {}", err),
195            })?;
196        let apollo_id: Option<Uuid> = extract_key_value_from_object!(
197            object,
198            "apolloId",
199            Value::String(s) => s
200        )
201        .map_err(|err| MalformedResponseError {
202            reason: format!("invalid `apolloId` within error: {}", err),
203        })?
204        .map(|s| {
205            Uuid::from_str(s.as_str()).map_err(|err| MalformedResponseError {
206                reason: format!("invalid `apolloId` within error: {}", err),
207            })
208        })
209        .transpose()?;
210
211        Ok(Self::new(
212            message, locations, path, None, extensions, apollo_id,
213        ))
214    }
215
216    pub(crate) fn from_value_completion_value(value: &Value) -> Option<Error> {
217        let value_completion = ensure_object!(value).ok()?;
218        let mut extensions = value_completion
219            .get("extensions")
220            .and_then(|e: &Value| -> Option<Object> {
221                serde_json_bytes::from_value(e.clone()).ok()
222            })
223            .unwrap_or_default();
224        extensions.insert("code", ERROR_CODE_RESPONSE_VALIDATION.into());
225        extensions.insert("severity", tracing::Level::WARN.as_str().into());
226
227        let message = value_completion
228            .get("message")
229            .and_then(|m| m.as_str())
230            .map(|m| m.to_string())
231            .unwrap_or_default();
232        let locations = value_completion
233            .get("locations")
234            .map(|l: &Value| skip_invalid_locations(l.clone()))
235            .map(|l: Value| serde_json_bytes::from_value(l).unwrap_or_default())
236            .unwrap_or_default();
237        let path =
238            value_completion
239                .get("path")
240                .and_then(|p: &serde_json_bytes::Value| -> Option<Path> {
241                    serde_json_bytes::from_value(p.clone()).ok()
242                });
243
244        Some(Self::new(
245            message, locations, path, None, extensions,
246            None, // apollo_id is not serialized, so it will never exist in a serialized vc error
247        ))
248    }
249
250    /// Extract the error code from [`Error::extensions`] as a String if it is set.
251    pub fn extension_code(&self) -> Option<String> {
252        self.extensions.get("code").and_then(|c| match c {
253            Value::String(s) => Some(s.as_str().to_owned()),
254            Value::Number(n) => Some(n.to_string()),
255            Value::Null | Value::Array(_) | Value::Object(_) | Value::Bool(_) => None,
256        })
257    }
258
259    /// Retrieve the internal Apollo unique ID for this error
260    pub fn apollo_id(&self) -> Uuid {
261        self.apollo_id
262    }
263
264    /// Returns a duplicate of the error where [`self.apollo_id`] is now the given ID
265    pub fn with_apollo_id(&self, id: Uuid) -> Self {
266        let mut new_err = self.clone();
267        new_err.apollo_id = id;
268        new_err
269    }
270
271    #[cfg(test)]
272    /// Returns a duplicate of the error where [`self.apollo_id`] is `Uuid::nil()`. Used for
273    /// comparing errors in tests where you cannot control the randomly generated Uuid
274    pub fn with_null_id(&self) -> Self {
275        self.with_apollo_id(Uuid::nil())
276    }
277}
278
279/// Generate a random Uuid. For use in generating a default [`Error::apollo_id`] when not supplied
280/// during deserialization.
281fn generate_uuid() -> Uuid {
282    Uuid::new_v4()
283}
284
285/// GraphQL spec require that both "line" and "column" are positive numbers.
286/// However GraphQL Java and GraphQL Kotlin return `{ "line": -1, "column": -1 }`
287/// if they can't determine error location inside query.
288/// This function removes such locations from supplied value.
289fn skip_invalid_locations(mut value: Value) -> Value {
290    if let Some(array) = value.as_array_mut() {
291        array.retain(|location| {
292            location.get("line") != Some(&Value::from(-1))
293                || location.get("column") != Some(&Value::from(-1))
294        })
295    }
296    value
297}
298
299/// Displays (only) the error message.
300impl fmt::Display for Error {
301    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302        self.message.fmt(f)
303    }
304}
305
306/// Trait used to convert expected errors into a list of GraphQL errors
307pub(crate) trait IntoGraphQLErrors
308where
309    Self: Sized,
310{
311    fn into_graphql_errors(self) -> Result<Vec<Error>, Self>;
312}
313
314/// Trait used to get extension type from an error
315pub(crate) trait ErrorExtension
316where
317    Self: Sized,
318{
319    fn extension_code(&self) -> String {
320        std::any::type_name::<Self>().to_shouty_snake_case()
321    }
322
323    fn custom_extension_details(&self) -> Option<Object> {
324        None
325    }
326}
327
328impl From<CompilerExecutionError> for Error {
329    fn from(error: CompilerExecutionError) -> Self {
330        let CompilerExecutionError {
331            message,
332            locations,
333            path,
334            extensions,
335        } = error;
336        let locations = locations
337            .into_iter()
338            .map(|location| Location {
339                line: location.line as u32,
340                column: location.column as u32,
341            })
342            .collect::<Vec<_>>();
343        let path = if !path.is_empty() {
344            let elements = path
345                .into_iter()
346                .map(|element| match element {
347                    ResponseDataPathSegment::Field(name) => {
348                        JsonPathElement::Key(name.as_str().to_owned(), None)
349                    }
350                    ResponseDataPathSegment::ListIndex(i) => JsonPathElement::Index(i),
351                })
352                .collect();
353            Some(Path(elements))
354        } else {
355            None
356        };
357        Self {
358            message,
359            locations,
360            path,
361            extensions,
362            apollo_id: Uuid::new_v4(),
363        }
364    }
365}
366
367/// Assert that the expected and actual [`Error`] are equal when ignoring their
368/// [`Error::apollo_id`].
369#[macro_export]
370macro_rules! assert_error_eq_ignoring_id {
371    ($expected:expr, $actual:expr) => {
372        assert_eq!($expected.with_null_id(), $actual.with_null_id());
373    };
374}
375
376/// Assert that the expected and actual lists of [`Error`] are equal when ignoring their
377/// [`Error::apollo_id`].
378#[macro_export]
379macro_rules! assert_errors_eq_ignoring_id {
380    ($expected:expr, $actual:expr) => {{
381        let normalize =
382            |v: &[graphql::Error]| v.iter().map(|e| e.with_null_id()).collect::<Vec<_>>();
383
384        assert_eq!(normalize(&$expected), normalize(&$actual));
385    }};
386}
387
388/// Assert that the expected and actual [`Response`] are equal when ignoring the
389/// [`Error::apollo_id`] on any [`Error`] in their [`Response::errors`].
390#[macro_export]
391macro_rules! assert_response_eq_ignoring_error_id {
392    ($expected:expr, $actual:expr) => {{
393        let normalize =
394            |v: &[graphql::Error]| v.iter().map(|e| e.with_null_id()).collect::<Vec<_>>();
395        let mut expected_response: graphql::Response = $expected.clone();
396        let mut actual_response: graphql::Response = $actual.clone();
397        expected_response.errors = normalize(&expected_response.errors);
398        actual_response.errors = normalize(&actual_response.errors);
399
400        assert_eq!(expected_response, actual_response);
401    }};
402}