Skip to main content

apollo_compiler/
response.rs

1//! GraphQL [responses](https://spec.graphql.org/September2025/#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/September2025/#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/September2025/#sec-Execution)
29/// of selection sets and fields.
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31#[serde(deny_unknown_fields)]
32pub struct ExecutionResponse {
33    // A note in <https://spec.graphql.org/September2025/#sec-Execution-Result>
34    // suggests serializing this first
35    #[serde(skip_serializing_if = "Vec::is_empty")]
36    #[serde(default)]
37    pub errors: Vec<GraphQLError>,
38
39    pub data: Option<JsonMap>,
40}
41
42/// A serializable [error](https://spec.graphql.org/September2025/#sec-Errors.Error-Result-Format),
43/// as found in a GraphQL response.
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45#[serde(deny_unknown_fields)]
46pub struct GraphQLError {
47    /// The error message.
48    pub message: String,
49
50    /// Locations in relevant to the error, if any.
51    #[serde(skip_serializing_if = "Vec::is_empty")]
52    #[serde(default)]
53    pub locations: Vec<LineColumn>,
54
55    /// If non-empty, the error is an [execution error]
56    /// for the particular field found at this path in [`ExecutionResponse::data`].
57    ///
58    /// [execution error]: https://spec.graphql.org/September2025/#sec-Errors.Execution-Errors
59    #[serde(skip_serializing_if = "Vec::is_empty")]
60    #[serde(default)]
61    pub path: Vec<ResponseDataPathSegment>,
62
63    /// Reserved for any additional information
64    #[serde(skip_serializing_if = "JsonMap::is_empty")]
65    #[serde(default)]
66    pub extensions: JsonMap,
67}
68
69/// A `Vec<ResponseDataPathSegment>` like in [`GraphQLError::path`]
70/// represents a [path](https://spec.graphql.org/September2025/#sec-Errors.Error-Result-Format)
71/// into [`ExecutionResponse::data`],
72/// starting at the root and indexing into increasingly nested JSON objects or arrays.
73///
74/// # Example
75///
76/// In a GraphQL response like this:
77///
78/// ```json
79/// {
80///   "data": {
81///     "players": [
82///       {"name": "Alice"},
83///       {"name": "Bob"}
84///     ]
85///   },
86///   "errors": [
87///     {
88///       "message": "Something went wrong",
89///       "path": ["players", 1, "name"]
90///     }
91///   ]
92/// }
93/// ```
94///
95/// The error path would have a Rust representation like
96/// `vec![Field("players"), ListIndex(1), Field("name")]`
97/// and designate the value `"name": "Bob"`.
98#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
99#[serde(untagged)]
100pub enum ResponseDataPathSegment {
101    /// The relevant key in an object value
102    Field(crate::Name),
103
104    /// The index of the relevant item in a list value
105    ListIndex(usize),
106}
107
108impl GraphQLError {
109    pub fn new(
110        message: impl Into<String>,
111        location: Option<SourceSpan>,
112        sources: &SourceMap,
113    ) -> Self {
114        Self {
115            message: message.into(),
116            locations: location
117                .into_iter()
118                .filter_map(|location| location.line_column(sources))
119                .collect(),
120            path: Default::default(),
121            extensions: Default::default(),
122        }
123    }
124}