apollo_router/graphql/
mod.rs1mod request;
4mod response;
5mod visitor;
6
7use std::fmt;
8use std::pin::Pin;
9
10use apollo_compiler::response::GraphQLError as CompilerExecutionError;
11use apollo_compiler::response::ResponseDataPathSegment;
12use futures::Stream;
13use heck::ToShoutySnakeCase;
14pub use request::Request;
15pub use response::IncrementalResponse;
16use response::MalformedResponseError;
17pub use response::Response;
18use serde::Deserialize;
19use serde::Serialize;
20use serde_json_bytes::ByteString;
21use serde_json_bytes::Map as JsonMap;
22use serde_json_bytes::Value;
23pub(crate) use visitor::ResponseVisitor;
24
25use crate::json_ext::Object;
26use crate::json_ext::Path;
27pub use crate::json_ext::Path as JsonPath;
28pub use crate::json_ext::PathElement as JsonPathElement;
29
30pub type ResponseStream = Pin<Box<dyn Stream<Item = Response> + Send>>;
39
40#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
41#[serde(rename_all = "camelCase")]
42pub struct Location {
44 pub line: u32,
46 pub column: u32,
48}
49
50#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)]
55#[serde(rename_all = "camelCase")]
56#[non_exhaustive]
57pub struct Error {
58 pub message: String,
60
61 #[serde(skip_serializing_if = "Vec::is_empty", default)]
63 pub locations: Vec<Location>,
64
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub path: Option<Path>,
68
69 #[serde(default, skip_serializing_if = "Object::is_empty")]
71 pub extensions: Object,
72}
73#[buildstructor::buildstructor]
76impl Error {
77 #[builder(visibility = "pub")]
108 fn new<T: Into<String>>(
109 message: String,
110 locations: Vec<Location>,
111 path: Option<Path>,
112 extension_code: T,
113 mut extensions: JsonMap<ByteString, Value>,
115 ) -> Self {
116 extensions
117 .entry("code")
118 .or_insert_with(|| extension_code.into().into());
119 Self {
120 message,
121 locations,
122 path,
123 extensions,
124 }
125 }
126
127 pub(crate) fn from_value(value: Value) -> Result<Error, MalformedResponseError> {
128 let mut object = ensure_object!(value).map_err(|error| MalformedResponseError {
129 reason: format!("invalid error within `errors`: {}", error),
130 })?;
131
132 let extensions =
133 extract_key_value_from_object!(object, "extensions", Value::Object(o) => o)
134 .map_err(|err| MalformedResponseError {
135 reason: format!("invalid `extensions` within error: {}", err),
136 })?
137 .unwrap_or_default();
138 let message = match extract_key_value_from_object!(object, "message", Value::String(s) => s)
139 {
140 Ok(Some(s)) => Ok(s.as_str().to_string()),
141 Ok(None) => Err(MalformedResponseError {
142 reason: "missing required `message` property within error".to_owned(),
143 }),
144 Err(err) => Err(MalformedResponseError {
145 reason: format!("invalid `message` within error: {}", err),
146 }),
147 }?;
148 let locations = extract_key_value_from_object!(object, "locations")
149 .map(skip_invalid_locations)
150 .map(serde_json_bytes::from_value)
151 .transpose()
152 .map_err(|err| MalformedResponseError {
153 reason: format!("invalid `locations` within error: {}", err),
154 })?
155 .unwrap_or_default();
156 let path = extract_key_value_from_object!(object, "path")
157 .map(serde_json_bytes::from_value)
158 .transpose()
159 .map_err(|err| MalformedResponseError {
160 reason: format!("invalid `path` within error: {}", err),
161 })?;
162
163 Ok(Error {
164 message,
165 locations,
166 path,
167 extensions,
168 })
169 }
170}
171
172fn skip_invalid_locations(mut value: Value) -> Value {
177 if let Some(array) = value.as_array_mut() {
178 array.retain(|location| {
179 location.get("line") != Some(&Value::from(-1))
180 || location.get("column") != Some(&Value::from(-1))
181 })
182 }
183 value
184}
185
186impl fmt::Display for Error {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 self.message.fmt(f)
190 }
191}
192
193pub(crate) trait IntoGraphQLErrors
195where
196 Self: Sized,
197{
198 fn into_graphql_errors(self) -> Result<Vec<Error>, Self>;
199}
200
201pub(crate) trait ErrorExtension
203where
204 Self: Sized,
205{
206 fn extension_code(&self) -> String {
207 std::any::type_name::<Self>().to_shouty_snake_case()
208 }
209
210 fn custom_extension_details(&self) -> Option<Object> {
211 None
212 }
213}
214
215impl From<CompilerExecutionError> for Error {
216 fn from(error: CompilerExecutionError) -> Self {
217 let CompilerExecutionError {
218 message,
219 locations,
220 path,
221 extensions,
222 } = error;
223 let locations = locations
224 .into_iter()
225 .map(|location| Location {
226 line: location.line as u32,
227 column: location.column as u32,
228 })
229 .collect::<Vec<_>>();
230 let path = if !path.is_empty() {
231 let elements = path
232 .into_iter()
233 .map(|element| match element {
234 ResponseDataPathSegment::Field(name) => {
235 JsonPathElement::Key(name.as_str().to_owned(), None)
236 }
237 ResponseDataPathSegment::ListIndex(i) => JsonPathElement::Index(i),
238 })
239 .collect();
240 Some(Path(elements))
241 } else {
242 None
243 };
244 Self {
245 message,
246 locations,
247 path,
248 extensions,
249 }
250 }
251}