async_graphql/
error.rs

1use std::{
2    any::Any,
3    collections::BTreeMap,
4    fmt::{self, Debug, Display, Formatter},
5    marker::PhantomData,
6    sync::Arc,
7};
8
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12use crate::{InputType, Pos, Value, parser};
13
14/// Extensions to the error.
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
16#[serde(transparent)]
17pub struct ErrorExtensionValues(BTreeMap<String, Value>);
18
19impl ErrorExtensionValues {
20    /// Set an extension value.
21    pub fn set(&mut self, name: impl AsRef<str>, value: impl Into<Value>) {
22        self.0.insert(name.as_ref().to_string(), value.into());
23    }
24
25    /// Unset an extension value.
26    pub fn unset(&mut self, name: impl AsRef<str>) {
27        self.0.remove(name.as_ref());
28    }
29
30    /// Get an extension value.
31    pub fn get(&self, name: impl AsRef<str>) -> Option<&Value> {
32        self.0.get(name.as_ref())
33    }
34}
35
36/// An error in a GraphQL server.
37#[derive(Clone, Serialize, Deserialize)]
38pub struct ServerError {
39    /// An explanatory message of the error.
40    pub message: String,
41    /// The source of the error.
42    #[serde(skip)]
43    pub source: Option<Arc<dyn Any + Send + Sync>>,
44    /// Where the error occurred.
45    #[serde(skip_serializing_if = "Vec::is_empty", default)]
46    pub locations: Vec<Pos>,
47    /// If the error occurred in a resolver, the path to the error.
48    #[serde(skip_serializing_if = "Vec::is_empty", default)]
49    pub path: Vec<PathSegment>,
50    /// Extensions to the error.
51    #[serde(skip_serializing_if = "error_extensions_is_empty", default)]
52    pub extensions: Option<ErrorExtensionValues>,
53}
54
55fn error_extensions_is_empty(values: &Option<ErrorExtensionValues>) -> bool {
56    values.as_ref().is_none_or(|values| values.0.is_empty())
57}
58
59impl Debug for ServerError {
60    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
61        f.debug_struct("ServerError")
62            .field("message", &self.message)
63            .field("locations", &self.locations)
64            .field("path", &self.path)
65            .field("extensions", &self.extensions)
66            .finish()
67    }
68}
69
70impl PartialEq for ServerError {
71    fn eq(&self, other: &Self) -> bool {
72        self.message.eq(&other.message)
73            && self.locations.eq(&other.locations)
74            && self.path.eq(&other.path)
75            && self.extensions.eq(&other.extensions)
76    }
77}
78
79impl ServerError {
80    /// Create a new server error with the message.
81    pub fn new(message: impl Into<String>, pos: Option<Pos>) -> Self {
82        Self {
83            message: message.into(),
84            source: None,
85            locations: pos.map(|pos| vec![pos]).unwrap_or_default(),
86            path: Vec::new(),
87            extensions: None,
88        }
89    }
90
91    /// Get the source of the error.
92    ///
93    /// # Examples
94    ///
95    /// ```rust
96    /// use std::io::ErrorKind;
97    ///
98    /// use async_graphql::*;
99    ///
100    /// struct Query;
101    ///
102    /// #[Object]
103    /// impl Query {
104    ///     async fn value(&self) -> Result<i32> {
105    ///         Err(Error::new_with_source(std::io::Error::other("my error")))
106    ///     }
107    /// }
108    ///
109    /// let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
110    ///
111    /// # tokio::runtime::Runtime::new().unwrap().block_on(async move {
112    /// let err = schema
113    ///     .execute("{ value }")
114    ///     .await
115    ///     .into_result()
116    ///     .unwrap_err()
117    ///     .remove(0);
118    /// assert!(err.source::<std::io::Error>().is_some());
119    /// # });
120    /// ```
121    pub fn source<T: Any + Send + Sync>(&self) -> Option<&T> {
122        self.source.as_ref().map(|err| err.downcast_ref()).flatten()
123    }
124
125    #[doc(hidden)]
126    #[must_use]
127    pub fn with_path(self, path: Vec<PathSegment>) -> Self {
128        Self { path, ..self }
129    }
130}
131
132impl Display for ServerError {
133    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
134        f.write_str(&self.message)
135    }
136}
137
138impl From<ServerError> for Vec<ServerError> {
139    fn from(single: ServerError) -> Self {
140        vec![single]
141    }
142}
143
144impl From<parser::Error> for ServerError {
145    fn from(e: parser::Error) -> Self {
146        Self {
147            message: e.to_string(),
148            source: None,
149            locations: e.positions().collect(),
150            path: Vec::new(),
151            extensions: None,
152        }
153    }
154}
155
156/// A segment of path to a resolver.
157///
158/// This is like [`QueryPathSegment`](enum.QueryPathSegment.html), but owned and
159/// used as a part of errors instead of during execution.
160#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161#[serde(untagged)]
162pub enum PathSegment {
163    /// A field in an object.
164    Field(String),
165    /// An index in a list.
166    Index(usize),
167}
168
169/// Alias for `Result<T, ServerError>`.
170pub type ServerResult<T> = std::result::Result<T, ServerError>;
171
172/// An error parsing an input value.
173///
174/// This type is generic over T as it uses T's type name when converting to a
175/// regular error.
176#[derive(Debug)]
177pub struct InputValueError<T> {
178    message: String,
179    extensions: Option<ErrorExtensionValues>,
180    phantom: PhantomData<T>,
181}
182
183impl<T: InputType> InputValueError<T> {
184    fn new(message: String, extensions: Option<ErrorExtensionValues>) -> Self {
185        Self {
186            message,
187            extensions,
188            phantom: PhantomData,
189        }
190    }
191
192    /// The expected input type did not match the actual input type.
193    #[must_use]
194    pub fn expected_type(actual: Value) -> Self {
195        Self::new(
196            format!(
197                r#"Expected input type "{}", found {}."#,
198                T::type_name(),
199                actual
200            ),
201            None,
202        )
203    }
204
205    /// A custom error message.
206    ///
207    /// Any type that implements `Display` is automatically converted to this if
208    /// you use the `?` operator.
209    #[must_use]
210    pub fn custom(msg: impl Display) -> Self {
211        Self::new(
212            format!(r#"Failed to parse "{}": {}"#, T::type_name(), msg),
213            None,
214        )
215    }
216
217    /// Propagate the error message to a different type.
218    pub fn propagate<U: InputType>(self) -> InputValueError<U> {
219        if T::type_name() != U::type_name() {
220            InputValueError::new(
221                format!(
222                    r#"{} (occurred while parsing "{}")"#,
223                    self.message,
224                    U::type_name()
225                ),
226                self.extensions,
227            )
228        } else {
229            InputValueError::new(self.message, self.extensions)
230        }
231    }
232
233    /// Set an extension value.
234    pub fn with_extension(mut self, name: impl AsRef<str>, value: impl Into<Value>) -> Self {
235        self.extensions
236            .get_or_insert_with(ErrorExtensionValues::default)
237            .set(name, value);
238        self
239    }
240
241    /// Convert the error into a server error.
242    pub fn into_server_error(self, pos: Pos) -> ServerError {
243        let mut err = ServerError::new(self.message, Some(pos));
244        err.extensions = self.extensions;
245        err
246    }
247}
248
249impl<T: InputType, E: Display> From<E> for InputValueError<T> {
250    fn from(error: E) -> Self {
251        Self::custom(error)
252    }
253}
254
255/// An error parsing a value of type `T`.
256pub type InputValueResult<T> = Result<T, InputValueError<T>>;
257
258/// An error with a message and optional extensions.
259#[derive(Clone, Serialize)]
260pub struct Error {
261    /// The error message.
262    pub message: String,
263    /// The source of the error.
264    #[serde(skip)]
265    pub source: Option<Arc<dyn Any + Send + Sync>>,
266    /// Extensions to the error.
267    #[serde(skip_serializing_if = "error_extensions_is_empty")]
268    pub extensions: Option<ErrorExtensionValues>,
269}
270
271impl Debug for Error {
272    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
273        f.debug_struct("Error")
274            .field("message", &self.message)
275            .field("extensions", &self.extensions)
276            .finish()
277    }
278}
279
280impl PartialEq for Error {
281    fn eq(&self, other: &Self) -> bool {
282        self.message.eq(&other.message) && self.extensions.eq(&other.extensions)
283    }
284}
285
286impl Error {
287    /// Create an error from the given error message.
288    pub fn new(message: impl Into<String>) -> Self {
289        Self {
290            message: message.into(),
291            source: None,
292            extensions: None,
293        }
294    }
295
296    /// Create an error with a type that implements `Display`, and it will also
297    /// set the `source` of the error to this value.
298    pub fn new_with_source(source: impl Display + Send + Sync + 'static) -> Self {
299        Self {
300            message: source.to_string(),
301            source: Some(Arc::new(source)),
302            extensions: None,
303        }
304    }
305
306    /// Convert the error to a server error.
307    #[must_use]
308    pub fn into_server_error(self, pos: Pos) -> ServerError {
309        ServerError {
310            message: self.message,
311            source: self.source,
312            locations: vec![pos],
313            path: Vec::new(),
314            extensions: self.extensions,
315        }
316    }
317}
318
319#[cfg(not(feature = "custom-error-conversion"))]
320impl<T: Display + Send + Sync + 'static> From<T> for Error {
321    fn from(e: T) -> Self {
322        Self {
323            message: e.to_string(),
324            source: Some(Arc::new(e)),
325            extensions: None,
326        }
327    }
328}
329
330#[cfg(feature = "custom-error-conversion")]
331impl From<&'static str> for Error {
332    fn from(e: &'static str) -> Self {
333        Self {
334            message: e.to_string(),
335            source: None,
336            extensions: None,
337        }
338    }
339}
340
341#[cfg(feature = "custom-error-conversion")]
342impl From<String> for Error {
343    fn from(e: String) -> Self {
344        Self {
345            message: e,
346            source: None,
347            extensions: None,
348        }
349    }
350}
351
352/// An alias for `Result<T, Error>`.
353pub type Result<T, E = Error> = std::result::Result<T, E>;
354
355/// An error parsing the request.
356#[derive(Debug, Error)]
357#[non_exhaustive]
358pub enum ParseRequestError {
359    /// An IO error occurred.
360    #[error("{0}")]
361    Io(#[from] std::io::Error),
362
363    /// The request's syntax was invalid.
364    #[error("Invalid request: {0}")]
365    InvalidRequest(Box<dyn std::error::Error + Send + Sync>),
366
367    /// The request's files map was invalid.
368    #[error("Invalid files map: {0}")]
369    InvalidFilesMap(Box<dyn std::error::Error + Send + Sync>),
370
371    /// The request's multipart data was invalid.
372    #[error("Invalid multipart data")]
373    InvalidMultipart(multer::Error),
374
375    /// Missing "operators" part for multipart request.
376    #[error("Missing \"operators\" part")]
377    MissingOperatorsPart,
378
379    /// Missing "map" part for multipart request.
380    #[error("Missing \"map\" part")]
381    MissingMapPart,
382
383    /// It's not an upload operation
384    #[error("It's not an upload operation")]
385    NotUpload,
386
387    /// Files were missing the request.
388    #[error("Missing files")]
389    MissingFiles,
390
391    /// The request's payload is too large, and this server rejected it.
392    #[error("Payload too large")]
393    PayloadTooLarge,
394
395    /// The request is a batch request, but the server does not support batch
396    /// requests.
397    #[error("Batch requests are not supported")]
398    UnsupportedBatch,
399}
400
401impl From<multer::Error> for ParseRequestError {
402    fn from(err: multer::Error) -> Self {
403        match err {
404            multer::Error::FieldSizeExceeded { .. } | multer::Error::StreamSizeExceeded { .. } => {
405                ParseRequestError::PayloadTooLarge
406            }
407            _ => ParseRequestError::InvalidMultipart(err),
408        }
409    }
410}
411
412impl From<mime::FromStrError> for ParseRequestError {
413    fn from(e: mime::FromStrError) -> Self {
414        Self::InvalidRequest(Box::new(e))
415    }
416}
417
418/// An error which can be extended into a `Error`.
419pub trait ErrorExtensions: Sized {
420    /// Convert the error to a `Error`.
421    fn extend(&self) -> Error;
422
423    /// Add extensions to the error, using a callback to make the extensions.
424    fn extend_with<C>(self, cb: C) -> Error
425    where
426        C: FnOnce(&Self, &mut ErrorExtensionValues),
427    {
428        let mut new_extensions = Default::default();
429        cb(&self, &mut new_extensions);
430
431        let Error {
432            message,
433            source,
434            extensions,
435        } = self.extend();
436
437        let mut extensions = extensions.unwrap_or_default();
438        extensions.0.extend(new_extensions.0);
439
440        Error {
441            message,
442            source,
443            extensions: Some(extensions),
444        }
445    }
446}
447
448impl ErrorExtensions for Error {
449    fn extend(&self) -> Error {
450        self.clone()
451    }
452}
453
454// implementing for &E instead of E gives the user the possibility to implement
455// for E which does not conflict with this implementation acting as a fallback.
456impl<E: Display> ErrorExtensions for &E {
457    fn extend(&self) -> Error {
458        Error {
459            message: self.to_string(),
460            source: None,
461            extensions: None,
462        }
463    }
464}
465
466/// Extend a `Result`'s error value with
467/// [`ErrorExtensions`](trait.ErrorExtensions.html).
468pub trait ResultExt<T, E>: Sized {
469    /// Extend the error value of the result with the callback.
470    fn extend_err<C>(self, cb: C) -> Result<T>
471    where
472        C: FnOnce(&E, &mut ErrorExtensionValues);
473
474    /// Extend the result to a `Result`.
475    fn extend(self) -> Result<T>;
476}
477
478// This is implemented on E and not &E which means it cannot be used on foreign
479// types. (see example).
480impl<T, E> ResultExt<T, E> for std::result::Result<T, E>
481where
482    E: ErrorExtensions + Send + Sync + 'static,
483{
484    fn extend_err<C>(self, cb: C) -> Result<T>
485    where
486        C: FnOnce(&E, &mut ErrorExtensionValues),
487    {
488        match self {
489            Err(err) => Err(err.extend_with(|e, ee| cb(e, ee))),
490            Ok(value) => Ok(value),
491        }
492    }
493
494    fn extend(self) -> Result<T> {
495        match self {
496            Err(err) => Err(err.extend()),
497            Ok(value) => Ok(value),
498        }
499    }
500}