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 #[serde(skip)]
84 span_event_emitted: bool,
85}
86
87impl Default for Error {
88 fn default() -> Self {
89 Self {
90 message: String::new(),
91 locations: Vec::new(),
92 path: None,
93 extensions: Object::new(),
94 apollo_id: generate_uuid(),
95 span_event_emitted: false,
96 }
97 }
98}
99
100#[buildstructor::buildstructor]
103impl Error {
104 #[builder(visibility = "pub")]
145 fn new(
146 message: String,
147 locations: Vec<Location>,
148 path: Option<Path>,
149 extension_code: Option<String>,
150 mut extensions: JsonMap<ByteString, Value>,
152 apollo_id: Option<Uuid>,
153 ) -> Self {
154 if let Some(code) = extension_code {
155 extensions
156 .entry("code")
157 .or_insert(Value::String(ByteString::from(code)));
158 }
159 Self {
160 message,
161 locations,
162 path,
163 extensions,
164 apollo_id: apollo_id.unwrap_or_else(Uuid::new_v4),
165 span_event_emitted: false,
166 }
167 }
168
169 pub(crate) fn from_value(value: Value) -> Result<Error, MalformedResponseError> {
170 let mut object = ensure_object!(value).map_err(|error| MalformedResponseError {
171 reason: format!("invalid error within `errors`: {error}"),
172 })?;
173
174 let extensions =
175 extract_key_value_from_object!(object, "extensions", Value::Object(o) => o)
176 .map_err(|err| MalformedResponseError {
177 reason: format!("invalid `extensions` within error: {err}"),
178 })?
179 .unwrap_or_default();
180 let message = match extract_key_value_from_object!(object, "message", Value::String(s) => s)
181 {
182 Ok(Some(s)) => Ok(s.as_str().to_string()),
183 Ok(None) => Err(MalformedResponseError {
184 reason: "missing required `message` property within error".to_owned(),
185 }),
186 Err(err) => Err(MalformedResponseError {
187 reason: format!("invalid `message` within error: {err}"),
188 }),
189 }?;
190 let locations = extract_key_value_from_object!(object, "locations")
191 .map(skip_invalid_locations)
192 .map(serde_json_bytes::from_value)
193 .transpose()
194 .map_err(|err| MalformedResponseError {
195 reason: format!("invalid `locations` within error: {err}"),
196 })?
197 .unwrap_or_default();
198 let path = extract_key_value_from_object!(object, "path")
199 .map(serde_json_bytes::from_value)
200 .transpose()
201 .map_err(|err| MalformedResponseError {
202 reason: format!("invalid `path` within error: {err}"),
203 })?;
204 let apollo_id: Option<Uuid> = extract_key_value_from_object!(
205 object,
206 "apolloId",
207 Value::String(s) => s
208 )
209 .map_err(|err| MalformedResponseError {
210 reason: format!("invalid `apolloId` within error: {err}"),
211 })?
212 .map(|s| {
213 Uuid::from_str(s.as_str()).map_err(|err| MalformedResponseError {
214 reason: format!("invalid `apolloId` within error: {err}"),
215 })
216 })
217 .transpose()?;
218
219 Ok(Self::new(
220 message, locations, path, None, extensions, apollo_id,
221 ))
222 }
223
224 pub(crate) fn from_value_completion_value(value: &Value) -> Option<Error> {
225 let value_completion = ensure_object!(value).ok()?;
226 let mut extensions = value_completion
227 .get("extensions")
228 .and_then(|e: &Value| -> Option<Object> {
229 serde_json_bytes::from_value(e.clone()).ok()
230 })
231 .unwrap_or_default();
232 extensions.insert("code", ERROR_CODE_RESPONSE_VALIDATION.into());
233 extensions.insert("severity", tracing::Level::WARN.as_str().into());
234
235 let message = value_completion
236 .get("message")
237 .and_then(|m| m.as_str())
238 .map(|m| m.to_string())
239 .unwrap_or_default();
240 let locations = value_completion
241 .get("locations")
242 .map(|l: &Value| skip_invalid_locations(l.clone()))
243 .map(|l: Value| serde_json_bytes::from_value(l).unwrap_or_default())
244 .unwrap_or_default();
245 let path =
246 value_completion
247 .get("path")
248 .and_then(|p: &serde_json_bytes::Value| -> Option<Path> {
249 serde_json_bytes::from_value(p.clone()).ok()
250 });
251
252 Some(Self::new(
253 message, locations, path, None, extensions,
254 None, ))
256 }
257
258 pub fn extension_code(&self) -> Option<String> {
260 self.extensions.get("code").and_then(|c| match c {
261 Value::String(s) => Some(s.as_str().to_owned()),
262 Value::Number(n) => Some(n.to_string()),
263 Value::Null | Value::Array(_) | Value::Object(_) | Value::Bool(_) => None,
264 })
265 }
266
267 pub fn apollo_id(&self) -> Uuid {
269 self.apollo_id
270 }
271
272 pub fn with_apollo_id(&self, id: Uuid) -> Self {
274 let mut new_err = self.clone();
275 new_err.apollo_id = id;
276 new_err
277 }
278
279 pub(crate) fn span_event_emitted(&self) -> bool {
280 self.span_event_emitted
281 }
282
283 pub(crate) fn set_span_event_emitted(&mut self, value: bool) {
284 self.span_event_emitted = value;
285 }
286
287 #[cfg(test)]
288 pub fn with_null_id(&self) -> Self {
291 self.with_apollo_id(Uuid::nil())
292 }
293}
294
295fn generate_uuid() -> Uuid {
298 Uuid::new_v4()
299}
300
301fn skip_invalid_locations(mut value: Value) -> Value {
306 if let Some(array) = value.as_array_mut() {
307 array.retain(|location| {
308 location.get("line") != Some(&Value::from(-1))
309 || location.get("column") != Some(&Value::from(-1))
310 })
311 }
312 value
313}
314
315impl fmt::Display for Error {
317 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
318 self.message.fmt(f)
319 }
320}
321
322pub(crate) trait IntoGraphQLErrors
324where
325 Self: Sized,
326{
327 fn into_graphql_errors(self) -> Result<Vec<Error>, Self>;
328}
329
330pub(crate) trait ErrorExtension
332where
333 Self: Sized,
334{
335 fn extension_code(&self) -> String {
336 std::any::type_name::<Self>().to_shouty_snake_case()
337 }
338
339 fn custom_extension_details(&self) -> Option<Object> {
340 None
341 }
342}
343
344impl From<CompilerExecutionError> for Error {
345 fn from(error: CompilerExecutionError) -> Self {
346 let CompilerExecutionError {
347 message,
348 locations,
349 path,
350 extensions,
351 } = error;
352 let locations = locations
353 .into_iter()
354 .map(|location| Location {
355 line: location.line as u32,
356 column: location.column as u32,
357 })
358 .collect::<Vec<_>>();
359 let path = if !path.is_empty() {
360 let elements = path
361 .into_iter()
362 .map(|element| match element {
363 ResponseDataPathSegment::Field(name) => {
364 JsonPathElement::Key(name.as_str().to_owned(), None)
365 }
366 ResponseDataPathSegment::ListIndex(i) => JsonPathElement::Index(i),
367 })
368 .collect();
369 Some(Path(elements))
370 } else {
371 None
372 };
373 Self {
374 message,
375 locations,
376 path,
377 extensions,
378 apollo_id: Uuid::new_v4(),
379 span_event_emitted: false,
380 }
381 }
382}
383
384#[macro_export]
387macro_rules! assert_error_eq_ignoring_id {
388 ($expected:expr, $actual:expr) => {
389 assert_eq!($expected.with_null_id(), $actual.with_null_id());
390 };
391}
392
393#[macro_export]
396macro_rules! assert_errors_eq_ignoring_id {
397 ($expected:expr, $actual:expr) => {{
398 let normalize =
399 |v: &[graphql::Error]| v.iter().map(|e| e.with_null_id()).collect::<Vec<_>>();
400
401 assert_eq!(normalize(&$expected), normalize(&$actual));
402 }};
403}
404
405#[macro_export]
408macro_rules! assert_response_eq_ignoring_error_id {
409 ($expected:expr, $actual:expr) => {{
410 let normalize =
411 |v: &[graphql::Error]| v.iter().map(|e| e.with_null_id()).collect::<Vec<_>>();
412 let mut expected_response: graphql::Response = $expected.clone();
413 let mut actual_response: graphql::Response = $actual.clone();
414 expected_response.errors = normalize(&expected_response.errors);
415 actual_response.errors = normalize(&actual_response.errors);
416
417 assert_eq!(expected_response, actual_response);
418 }};
419}