async-graphql/src/error.rs

470 lines
12 KiB
Rust
Raw Normal View History

2020-07-15 10:05:24 +00:00
use crate::{Pos, QueryPathNode, Value};
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
use std::ops::Deref;
use thiserror::Error;
/// An error in the format of an input value.
#[derive(Debug)]
pub enum InputValueError {
/// Custom input value parsing error.
Custom(String),
/// The type of input value does not match the expectation. Contains the value that was found.
ExpectedType(Value),
}
impl<T: Display> From<T> for InputValueError {
fn from(err: T) -> Self {
InputValueError::Custom(err.to_string())
}
}
impl InputValueError {
/// Convert this error to a regular `Error` type.
pub fn into_error(self, pos: Pos, expected_type: String) -> Error {
match self {
InputValueError::Custom(reason) => Error::Query {
pos,
path: None,
err: QueryError::ParseInputValue { reason },
},
InputValueError::ExpectedType(value) => Error::Query {
pos,
path: None,
err: QueryError::ExpectedInputType {
expect: expected_type,
actual: value,
},
},
}
}
}
/// An alias for `Result<T, InputValueError>`.
pub type InputValueResult<T> = std::result::Result<T, InputValueError>;
/// An error in a field resolver.
2020-04-02 04:57:53 +00:00
#[derive(Clone, Debug)]
pub struct FieldError(pub String, pub Option<serde_json::Value>);
impl FieldError {
#[doc(hidden)]
pub fn into_error(self, pos: Pos) -> Error {
Error::Query {
pos,
path: None,
err: QueryError::FieldError {
err: self.0,
extended_error: self.1,
},
}
}
2020-03-01 10:54:34 +00:00
#[doc(hidden)]
2020-07-15 10:05:24 +00:00
pub fn into_error_with_path(self, pos: Pos, path: Option<&QueryPathNode<'_>>) -> Error {
Error::Query {
pos,
2020-07-15 10:05:24 +00:00
path: path.and_then(|path| serde_json::to_value(path).ok()),
err: QueryError::FieldError {
err: self.0,
extended_error: self.1,
},
}
}
}
2020-09-17 00:51:46 +00:00
/// An alias for `Result<T, FieldError>`.
pub type FieldResult<T> = std::result::Result<T, FieldError>;
impl<E: Display> From<E> for FieldError {
fn from(err: E) -> Self {
2020-04-02 04:57:53 +00:00
FieldError(format!("{}", err), None)
}
}
2020-09-06 05:38:31 +00:00
/// An error which can be extended into a `FieldError`.
pub trait ErrorExtensions: Sized {
2020-09-06 05:38:31 +00:00
/// Convert the error to a `FieldError`.
2020-04-02 04:57:53 +00:00
fn extend(&self) -> FieldError;
2020-09-06 05:38:31 +00:00
/// Add extensions to the error, using a callback to make the extensions.
2020-04-02 04:57:53 +00:00
fn extend_with<C>(self, cb: C) -> FieldError
where
C: FnOnce(&Self) -> serde_json::Value,
{
let name = self.extend().0;
if let Some(mut base) = self.extend().1 {
let mut cb_res = cb(&self);
if let Some(base_map) = base.as_object_mut() {
if let Some(cb_res_map) = cb_res.as_object_mut() {
base_map.append(cb_res_map);
}
return FieldError(name, Some(serde_json::json!(base_map)));
} else {
return FieldError(name, Some(cb_res));
}
}
FieldError(name, Some(cb(&self)))
}
}
impl ErrorExtensions for FieldError {
fn extend(&self) -> FieldError {
self.clone()
}
}
// implementing for &E instead of E gives the user the possibility to implement for E which does
// not conflict with this implementation acting as a fallback.
impl<E: std::fmt::Display> ErrorExtensions for &E {
fn extend(&self) -> FieldError {
FieldError(format!("{}", self), None)
}
}
/// Extend a `Result`'s error value with [`ErrorExtensions`](trait.ErrorExtensions.html).
pub trait ResultExt<T, E>: Sized {
/// Extend the error value of the result with the callback.
fn extend_err<C>(self, cb: C) -> FieldResult<T>
where
C: FnOnce(&E) -> serde_json::Value;
2020-04-02 04:57:53 +00:00
/// Extend the result to a `FieldResult`.
2020-04-02 04:57:53 +00:00
fn extend(self) -> FieldResult<T>;
}
2020-04-02 04:57:53 +00:00
// This is implemented on E and not &E which means it cannot be used on foreign types.
// (see example).
impl<T, E> ResultExt<T, E> for std::result::Result<T, E>
where
2020-04-02 04:57:53 +00:00
E: ErrorExtensions + Send + Sync + 'static,
{
fn extend_err<C>(self, cb: C) -> FieldResult<T>
where
C: FnOnce(&E) -> serde_json::Value,
{
match self {
2020-04-02 04:57:53 +00:00
Err(err) => Err(err.extend_with(|e| cb(e))),
Ok(value) => Ok(value),
}
}
fn extend(self) -> FieldResult<T> {
match self {
Err(err) => Err(err.extend()),
Ok(value) => Ok(value),
}
}
}
2020-03-01 10:54:34 +00:00
/// An error processing a GraphQL query.
2020-05-01 23:57:34 +00:00
#[derive(Debug, Error, PartialEq)]
2020-03-01 13:35:39 +00:00
pub enum QueryError {
/// The feature is not supported.
2020-03-01 10:54:34 +00:00
#[error("Not supported.")]
NotSupported,
/// The actual input type did not match the expected input type.
#[error("Expected input type \"{expect}\", found {actual}.")]
ExpectedInputType {
/// The expected input type.
2020-03-20 03:56:08 +00:00
expect: String,
/// The actual input type.
2020-03-20 03:56:08 +00:00
actual: Value,
},
2020-03-01 10:54:34 +00:00
/// Parsing of an input value failed.
#[error("Failed to parse input value: {reason}")]
2020-09-01 01:10:12 +00:00
ParseInputValue {
/// The reason for the failure to resolve.
reason: String,
},
/// A field was not found on an object type.
2020-03-01 10:54:34 +00:00
#[error("Cannot query field \"{field_name}\" on type \"{object}\".")]
2020-03-20 03:56:08 +00:00
FieldNotFound {
/// Field name
field_name: String,
/// Object name
object: String,
},
2020-03-01 10:54:34 +00:00
/// `operation_name` in the request was required but not provided.
#[error("Operation name required in request")]
RequiredOperationName,
2020-03-14 03:46:20 +00:00
/// The operation name was unknown.
2020-03-01 10:54:34 +00:00
#[error("Unknown operation named \"{name}\"")]
2020-03-20 03:56:08 +00:00
UnknownOperationNamed {
/// Operation name for query.
2020-03-20 03:56:08 +00:00
name: String,
},
2020-03-01 10:54:34 +00:00
/// The user attempted to query an object without selecting any subfields.
2020-03-01 10:54:34 +00:00
#[error("Type \"{object}\" must have a selection of subfields.")]
2020-03-20 03:56:08 +00:00
MustHaveSubFields {
/// Object name
object: String,
},
2020-03-01 10:54:34 +00:00
/// The schema does not have mutations.
2020-03-01 10:54:34 +00:00
#[error("Schema is not configured for mutations.")]
NotConfiguredMutations,
/// The schema does not have subscriptions.
2020-03-17 09:26:59 +00:00
#[error("Schema is not configured for subscriptions.")]
NotConfiguredSubscriptions,
/// The value does not exist in the enum.
2020-03-02 00:24:49 +00:00
#[error("Invalid value for enum \"{ty}\".")]
2020-03-20 03:56:08 +00:00
InvalidEnumValue {
/// Enum type name
ty: String,
/// Enum value
value: String,
},
2020-03-01 13:35:39 +00:00
/// A required field in an input object was not present.
2020-03-01 13:35:39 +00:00
#[error("Required field \"{field_name}\" for InputObject \"{object}\" does not exist.")]
RequiredField {
/// Field name
2020-03-01 13:35:39 +00:00
field_name: String,
2020-03-20 03:56:08 +00:00
/// Object name
2020-03-01 13:35:39 +00:00
object: &'static str,
},
/// A variable is used but not defined.
2020-03-01 13:35:39 +00:00
#[error("Variable \"${var_name}\" is not defined")]
2020-03-20 03:56:08 +00:00
VarNotDefined {
/// Variable name
var_name: String,
},
/// A directive was required but not provided.
#[error(
"Directive \"{directive}\" argument \"{arg_name}\" of type \"{arg_type}\" is required, but it was not provided."
)]
RequiredDirectiveArgs {
2020-03-20 03:56:08 +00:00
/// Directive name
directive: &'static str,
2020-03-20 03:56:08 +00:00
/// Argument name
arg_name: &'static str,
2020-03-20 03:56:08 +00:00
/// Argument type
arg_type: &'static str,
},
/// An unknown directive name was encountered.
#[error("Unknown directive \"{name}\".")]
2020-03-20 03:56:08 +00:00
UnknownDirective {
/// Directive name
name: String,
},
2020-03-05 07:50:57 +00:00
/// An unknown fragment was encountered.
2020-03-05 07:50:57 +00:00
#[error("Unknown fragment \"{name}\".")]
2020-03-20 03:56:08 +00:00
UnknownFragment {
/// Fragment name
2020-03-20 03:56:08 +00:00
name: String,
},
2020-03-07 02:39:55 +00:00
/// The query was too complex.
// TODO: Expand on this
#[error("Too complex")]
2020-03-25 07:07:16 +00:00
TooComplex,
/// The query was nested too deep.
#[error("Too deep")]
2020-03-25 07:07:16 +00:00
TooDeep,
2020-03-01 10:54:34 +00:00
/// A field handler errored.
#[error("Failed to resolve field: {err}")]
FieldError {
/// The error description.
2020-04-02 04:57:53 +00:00
err: String,
/// Extensions to the error provided through the [`ErrorExtensions`](trait.ErrorExtensions)
/// or [`ResultExt`](trait.ResultExt) traits.
extended_error: Option<serde_json::Value>,
},
2020-04-09 14:03:09 +00:00
2020-09-01 01:10:12 +00:00
/// Entity not found.
2020-04-09 14:03:09 +00:00
#[error("Entity not found")]
EntityNotFound,
2020-09-01 01:10:12 +00:00
/// "__typename" must be an existing string.
2020-04-09 14:03:09 +00:00
#[error("\"__typename\" must be an existing string")]
TypeNameNotExists,
2020-03-01 10:54:34 +00:00
}
impl QueryError {
/// Convert this error to a regular `Error` type.
pub fn into_error(self, pos: Pos) -> Error {
Error::Query {
pos,
path: None,
err: self,
2020-03-01 10:54:34 +00:00
}
}
}
/// An error parsing the request.
#[derive(Debug, Error)]
#[non_exhaustive]
2020-04-14 01:53:17 +00:00
pub enum ParseRequestError {
/// An IO error occurred.
#[error("{0}")]
2020-04-21 04:13:14 +00:00
Io(#[from] std::io::Error),
/// The request's syntax was invalid.
#[error("Invalid request: {0}")]
InvalidRequest(serde_json::Error),
/// The request's files map was invalid.
#[error("Invalid files map: {0}")]
InvalidFilesMap(serde_json::Error),
/// The request's multipart data was invalid.
2020-04-21 04:13:14 +00:00
#[error("Invalid multipart data")]
2020-09-14 18:38:41 +00:00
#[cfg(feature = "multipart")]
2020-09-15 18:32:13 +00:00
#[cfg_attr(feature = "nightly", doc(cfg(feature = "multipart")))]
2020-05-18 01:32:55 +00:00
InvalidMultipart(multer::Error),
2020-09-01 01:10:12 +00:00
/// Missing "operators" part for multipart request.
#[error("Missing \"operators\" part")]
MissingOperatorsPart,
2020-09-01 01:10:12 +00:00
/// Missing "map" part for multipart request.
#[error("Missing \"map\" part")]
MissingMapPart,
2020-09-01 01:10:12 +00:00
/// It's not an upload operation
#[error("It's not an upload operation")]
NotUpload,
/// Files were missing the request.
#[error("Missing files")]
MissingFiles,
2020-05-18 01:32:55 +00:00
/// The request's payload is too large, and this server rejected it.
2020-05-18 01:32:55 +00:00
#[error("Payload too large")]
PayloadTooLarge,
/// The request is a batch request, but the server does not support batch requests.
#[error("Batch requests are not supported")]
UnsupportedBatch,
}
2020-09-14 18:38:41 +00:00
#[cfg(feature = "multipart")]
2020-09-10 08:39:43 +00:00
impl From<multer::Error> for ParseRequestError {
fn from(err: multer::Error) -> Self {
match err {
multer::Error::FieldSizeExceeded { .. } | multer::Error::StreamSizeExceeded { .. } => {
ParseRequestError::PayloadTooLarge
}
_ => ParseRequestError::InvalidMultipart(err),
}
}
}
2020-09-01 01:10:12 +00:00
/// Verification error.
#[derive(Debug, PartialEq)]
pub struct RuleError {
2020-09-01 01:10:12 +00:00
/// Location of this error in query string.
pub locations: Vec<Pos>,
2020-09-01 01:10:12 +00:00
/// A description of this error.
pub message: String,
}
impl Display for RuleError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
for (idx, loc) in self.locations.iter().enumerate() {
if idx == 0 {
write!(f, "[")?;
} else {
write!(f, ", ")?;
}
write!(f, "{}:{}", loc.line, loc.column)?;
if idx == self.locations.len() - 1 {
write!(f, "] ")?;
}
}
write!(f, "{}", self.message)?;
Ok(())
}
}
/// An error serving a GraphQL query.
2020-05-01 23:57:34 +00:00
#[derive(Debug, Error, PartialEq)]
pub enum Error {
/// Parsing the query failed.
#[error("Parse error: {0}")]
Parse(#[from] crate::parser::Error),
/// Processing the query failed.
#[error("Query error: {err}")]
Query {
/// The position at which the processing failed.
pos: Pos,
2020-09-01 01:10:12 +00:00
/// Node path.
path: Option<serde_json::Value>,
2020-09-01 01:10:12 +00:00
/// The query error.
err: QueryError,
},
2020-09-01 01:10:12 +00:00
/// The query statement verification failed.
#[error("Rule error:\n{errors}")]
2020-09-01 01:10:12 +00:00
Rule {
/// List of errors.
errors: RuleErrors,
2020-09-01 01:10:12 +00:00
},
}
/// A collection of RuleError.
#[derive(Debug, PartialEq)]
pub struct RuleErrors(Vec<RuleError>);
impl From<Vec<RuleError>> for RuleErrors {
fn from(errors: Vec<RuleError>) -> Self {
Self(errors)
}
}
impl Deref for RuleErrors {
type Target = Vec<RuleError>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl IntoIterator for RuleErrors {
type Item = RuleError;
type IntoIter = std::vec::IntoIter<RuleError>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Display for RuleErrors {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
for err in &self.0 {
writeln!(f, " {}", err)?;
}
Ok(())
}
}