apollo_compiler/
response.rs

1//! GraphQL [responses](https://spec.graphql.org/draft/#sec-Response)
2//!
3//! This exists primarily to support [`introspection::partial_execute`].
4
5#[cfg(doc)]
6use crate::introspection;
7use crate::parser::LineColumn;
8use crate::parser::SourceMap;
9use crate::parser::SourceSpan;
10use serde::Deserialize;
11use serde::Serialize;
12/// Re-export of the version of the `serde_json_bytes` crate used for [`JsonValue`] and [`JsonMap`]
13pub use serde_json_bytes;
14
15/// A JSON-compatible dynamically-typed value.
16///
17/// Note: [`serde_json_bytes::Value`] is similar
18/// to [`serde_json::Value`][serde_json_bytes::serde_json::Value]
19/// but uses its reference-counted [`ByteString`][serde_json_bytes::ByteString]
20/// for string values and map keys.
21pub type JsonValue = serde_json_bytes::Value;
22
23/// A JSON-compatible object/map with string keys and dynamically-typed values.
24pub type JsonMap = serde_json_bytes::Map<serde_json_bytes::ByteString, JsonValue>;
25
26/// A [response](https://spec.graphql.org/October2021/#sec-Response-Format)
27/// to a GraphQL request that did not cause any [request error][crate::request::RequestError]
28/// and started [execution](https://spec.graphql.org/draft/#sec-Execution)
29/// of selection sets and fields.
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31#[serde(deny_unknown_fields)]
32pub struct ExecutionResponse {
33    // <https://spec.graphql.org/October2021/#note-6f005> suggests serializing this first
34    #[serde(skip_serializing_if = "Vec::is_empty")]
35    #[serde(default)]
36    pub errors: Vec<GraphQLError>,
37
38    pub data: Option<JsonMap>,
39}
40
41/// A serializable [error](https://spec.graphql.org/October2021/#sec-Errors.Error-result-format),
42/// as found in a GraphQL response.
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(deny_unknown_fields)]
45pub struct GraphQLError {
46    /// The error message.
47    pub message: String,
48
49    /// Locations in relevant to the error, if any.
50    #[serde(skip_serializing_if = "Vec::is_empty")]
51    #[serde(default)]
52    pub locations: Vec<LineColumn>,
53
54    /// If non-empty, the error is a [field error]
55    /// for the particular field found at this path in [`ExecutionResponse::data`].
56    ///
57    /// [field error]: https://spec.graphql.org/October2021/#sec-Errors.Field-errors
58    #[serde(skip_serializing_if = "Vec::is_empty")]
59    #[serde(default)]
60    pub path: Vec<ResponseDataPathSegment>,
61
62    /// Reserved for any additional information
63    #[serde(skip_serializing_if = "JsonMap::is_empty")]
64    #[serde(default)]
65    pub extensions: JsonMap,
66}
67
68/// A `Vec<ResponseDataPathSegment>` like in [`GraphQLError::path`]
69/// represents a [path](https://spec.graphql.org/draft/#sec-Errors.Error-Result-Format)
70/// into [`ExecutionResponse::data`],
71/// starting at the root and indexing into increasingly nested JSON objects or arrays.
72///
73/// # Example
74///
75/// In a GraphQL response like this:
76///
77/// ```json
78/// {
79///   "data": {
80///     "players": [
81///       {"name": "Alice"},
82///       {"name": "Bob"}
83///     ]
84///   },
85///   "errors": [
86///     {
87///       "message": "Something went wrong",
88///       "path": ["players", 1, "name"]
89///     }
90///   ]
91/// }
92/// ```
93///
94/// The error path would have a Rust representation like
95/// `vec![Field("players"), ListIndex(1), Field("name")]`
96/// and designate the value `"name": "Bob"`.
97#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
98#[serde(untagged)]
99pub enum ResponseDataPathSegment {
100    /// The relevant key in an object value
101    Field(crate::Name),
102
103    /// The index of the relevant item in a list value
104    ListIndex(usize),
105}
106
107impl GraphQLError {
108    pub fn new(
109        message: impl Into<String>,
110        location: Option<SourceSpan>,
111        sources: &SourceMap,
112    ) -> Self {
113        Self {
114            message: message.into(),
115            locations: location
116                .into_iter()
117                .filter_map(|location| location.line_column(sources))
118                .collect(),
119            path: Default::default(),
120            extensions: Default::default(),
121        }
122    }
123}