1mod 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
33pub type ResponseStream = Pin<Box<dyn Stream<Item = Response> + Send>>;
42
43#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
44#[serde(rename_all = "camelCase")]
45pub struct Location {
47 pub line: u32,
49 pub column: u32,
51}
52
53#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
58#[serde(rename_all = "camelCase", default)]
59#[non_exhaustive]
60pub struct Error {
61 pub message: String,
63
64 #[serde(skip_serializing_if = "Vec::is_empty")]
66 pub locations: Vec<Location>,
67
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub path: Option<Path>,
71
72 #[serde(skip_serializing_if = "Object::is_empty")]
74 pub extensions: Object,
75
76 #[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#[buildstructor::buildstructor]
96impl Error {
97 #[builder(visibility = "pub")]
138 fn new(
139 message: String,
140 locations: Vec<Location>,
141 path: Option<Path>,
142 extension_code: Option<String>,
143 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, ))
248 }
249
250 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 pub fn apollo_id(&self) -> Uuid {
261 self.apollo_id
262 }
263
264 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 pub fn with_null_id(&self) -> Self {
275 self.with_apollo_id(Uuid::nil())
276 }
277}
278
279fn generate_uuid() -> Uuid {
282 Uuid::new_v4()
283}
284
285fn 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
299impl fmt::Display for Error {
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 self.message.fmt(f)
303 }
304}
305
306pub(crate) trait IntoGraphQLErrors
308where
309 Self: Sized,
310{
311 fn into_graphql_errors(self) -> Result<Vec<Error>, Self>;
312}
313
314pub(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#[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#[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#[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}