Add Failure
type. #671
This commit is contained in:
parent
ad517eaddb
commit
e898998311
|
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Add `chrono::Duration` custom scalar. [#689](https://github.com/async-graphql/async-graphql/pull/689)
|
||||
- Implement `From<Option<Option<T>>>` for `MaybeUndefined<T>`.
|
||||
- Add `MaybeUndefined::as_opt_ref` and `MaybeUndefined::as_opt_deref` methods.
|
||||
- Add `Failure` type. [#671](https://github.com/async-graphql/async-graphql/issues/671)
|
||||
|
||||
## [2.11.0] 2021-11-03
|
||||
|
||||
|
|
78
src/error.rs
78
src/error.rs
|
@ -24,6 +24,11 @@ impl ErrorExtensionValues {
|
|||
pub struct ServerError {
|
||||
/// An explanatory message of the error.
|
||||
pub message: String,
|
||||
/// An explanatory message of the error. (for debug)
|
||||
///
|
||||
/// This message comes from [`Failure<T>`].
|
||||
#[serde(skip)]
|
||||
pub debug_message: Option<String>,
|
||||
/// Where the error occurred.
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub locations: Vec<Pos>,
|
||||
|
@ -44,12 +49,18 @@ impl ServerError {
|
|||
pub fn new(message: impl Into<String>, pos: Option<Pos>) -> Self {
|
||||
Self {
|
||||
message: message.into(),
|
||||
debug_message: None,
|
||||
locations: pos.map(|pos| vec![pos]).unwrap_or_default(),
|
||||
path: Vec::new(),
|
||||
extensions: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `debug_message` if it is `Some`, otherwise returns `message`.
|
||||
pub fn debug_message(&self) -> &str {
|
||||
self.debug_message.as_deref().unwrap_or(&self.message)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn with_path(self, path: Vec<PathSegment>) -> Self {
|
||||
Self { path, ..self }
|
||||
|
@ -72,6 +83,7 @@ impl From<parser::Error> for ServerError {
|
|||
fn from(e: parser::Error) -> Self {
|
||||
Self {
|
||||
message: e.to_string(),
|
||||
debug_message: None,
|
||||
locations: e.positions().collect(),
|
||||
path: Vec::new(),
|
||||
extensions: None,
|
||||
|
@ -164,6 +176,8 @@ pub type InputValueResult<T> = Result<T, InputValueError<T>>;
|
|||
pub struct Error {
|
||||
/// The error message.
|
||||
pub message: String,
|
||||
/// The debug error message.
|
||||
pub debug_message: Option<String>,
|
||||
/// Extensions to the error.
|
||||
#[serde(skip_serializing_if = "error_extensions_is_empty")]
|
||||
pub extensions: Option<ErrorExtensionValues>,
|
||||
|
@ -174,6 +188,7 @@ impl Error {
|
|||
pub fn new(message: impl Into<String>) -> Self {
|
||||
Self {
|
||||
message: message.into(),
|
||||
debug_message: None,
|
||||
extensions: None,
|
||||
}
|
||||
}
|
||||
|
@ -183,6 +198,7 @@ impl Error {
|
|||
pub fn into_server_error(self, pos: Pos) -> ServerError {
|
||||
ServerError {
|
||||
message: self.message,
|
||||
debug_message: self.debug_message,
|
||||
locations: vec![pos],
|
||||
path: Vec::new(),
|
||||
extensions: self.extensions,
|
||||
|
@ -194,6 +210,17 @@ impl<T: Display> From<T> for Error {
|
|||
fn from(e: T) -> Self {
|
||||
Self {
|
||||
message: e.to_string(),
|
||||
debug_message: None,
|
||||
extensions: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display + Debug> From<Failure<T>> for Error {
|
||||
fn from(err: Failure<T>) -> Self {
|
||||
Self {
|
||||
message: format!("{}", err.0),
|
||||
debug_message: Some(format!("{:?}", err.0)),
|
||||
extensions: None,
|
||||
}
|
||||
}
|
||||
|
@ -267,40 +294,57 @@ impl From<mime::FromStrError> for ParseRequestError {
|
|||
/// An error which can be extended into a `Error`.
|
||||
pub trait ErrorExtensions: Sized {
|
||||
/// Convert the error to a `Error`.
|
||||
fn extend(&self) -> Error;
|
||||
fn extend(self) -> Error;
|
||||
|
||||
/// Add extensions to the error, using a callback to make the extensions.
|
||||
fn extend_with<C>(self, cb: C) -> Error
|
||||
where
|
||||
C: FnOnce(&Self, &mut ErrorExtensionValues),
|
||||
{
|
||||
let message = self.extend().message;
|
||||
let mut extensions = self.extend().extensions.unwrap_or_default();
|
||||
cb(&self, &mut extensions);
|
||||
let mut new_extensions = Default::default();
|
||||
cb(&self, &mut new_extensions);
|
||||
|
||||
let Error {
|
||||
message,
|
||||
debug_message,
|
||||
extensions,
|
||||
} = self.extend();
|
||||
|
||||
let mut extensions = extensions.unwrap_or_default();
|
||||
extensions.0.extend(new_extensions.0);
|
||||
|
||||
Error {
|
||||
message,
|
||||
debug_message,
|
||||
extensions: Some(extensions),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorExtensions for Error {
|
||||
fn extend(&self) -> Error {
|
||||
self.clone()
|
||||
fn extend(self) -> Error {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// 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) -> Error {
|
||||
fn extend(self) -> Error {
|
||||
Error {
|
||||
message: self.to_string(),
|
||||
debug_message: None,
|
||||
extensions: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display + Debug> ErrorExtensions for Failure<T> {
|
||||
fn extend(self) -> Error {
|
||||
Error::from(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
@ -335,3 +379,23 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error type contains `T` that implements `Display` and `Debug`.
|
||||
///
|
||||
/// This type can solve the problem described in [#671](https://github.com/async-graphql/async-graphql/issues/671).
|
||||
#[derive(Debug)]
|
||||
pub struct Failure<T>(pub T);
|
||||
|
||||
impl<T: Display + Debug> From<T> for Failure<T> {
|
||||
fn from(err: T) -> Self {
|
||||
Self(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display + Debug> Failure<T> {
|
||||
/// Create a new failure.
|
||||
#[inline]
|
||||
pub fn new(err: T) -> Self {
|
||||
Self(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -209,7 +209,7 @@ pub use base::{
|
|||
Type, UnionType,
|
||||
};
|
||||
pub use error::{
|
||||
Error, ErrorExtensionValues, ErrorExtensions, InputValueError, InputValueResult,
|
||||
Error, ErrorExtensionValues, ErrorExtensions, Failure, InputValueError, InputValueResult,
|
||||
ParseRequestError, PathSegment, Result, ResultExt, ServerError, ServerResult,
|
||||
};
|
||||
pub use look_ahead::Lookahead;
|
||||
|
|
|
@ -851,6 +851,7 @@ impl From<RuleError> for ServerError {
|
|||
fn from(e: RuleError) -> Self {
|
||||
Self {
|
||||
message: e.message,
|
||||
debug_message: None,
|
||||
locations: e.locations,
|
||||
path: Vec::new(),
|
||||
extensions: e.extensions,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use async_graphql::*;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_error_extensions() {
|
||||
|
@ -75,3 +76,114 @@ pub async fn test_error_extensions() {
|
|||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
pub async fn test_failure() {
|
||||
#[derive(Debug)]
|
||||
struct MyError;
|
||||
|
||||
impl Display for MyError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "my error")
|
||||
}
|
||||
}
|
||||
|
||||
struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
async fn failure(&self) -> Result<i32> {
|
||||
Err(Failure(MyError).into())
|
||||
}
|
||||
|
||||
async fn failure2(&self) -> Result<i32> {
|
||||
Err(Failure(MyError))?;
|
||||
Ok(1)
|
||||
}
|
||||
|
||||
async fn failure3(&self) -> Result<i32> {
|
||||
Err(Failure(MyError)
|
||||
.extend_with(|_, values| values.set("a", 1))
|
||||
.extend_with(|_, values| values.set("b", 2)))?;
|
||||
Ok(1)
|
||||
}
|
||||
|
||||
async fn failure4(&self) -> Result<i32> {
|
||||
Err(Failure(MyError))
|
||||
.extend_err(|_, values| values.set("a", 1))
|
||||
.extend_err(|_, values| values.set("b", 2))?;
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
|
||||
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||
assert_eq!(
|
||||
schema
|
||||
.execute("{ failure }")
|
||||
.await
|
||||
.into_result()
|
||||
.unwrap_err(),
|
||||
vec![ServerError {
|
||||
message: "my error".to_string(),
|
||||
debug_message: Some("MyError".to_string()),
|
||||
locations: vec![Pos { line: 1, column: 3 }],
|
||||
path: vec![PathSegment::Field("failure".to_string())],
|
||||
extensions: None
|
||||
}]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
schema
|
||||
.execute("{ failure2 }")
|
||||
.await
|
||||
.into_result()
|
||||
.unwrap_err(),
|
||||
vec![ServerError {
|
||||
message: "my error".to_string(),
|
||||
debug_message: Some("MyError".to_string()),
|
||||
locations: vec![Pos { line: 1, column: 3 }],
|
||||
path: vec![PathSegment::Field("failure2".to_string())],
|
||||
extensions: None
|
||||
}]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
schema
|
||||
.execute("{ failure3 }")
|
||||
.await
|
||||
.into_result()
|
||||
.unwrap_err(),
|
||||
vec![ServerError {
|
||||
message: "my error".to_string(),
|
||||
debug_message: Some("MyError".to_string()),
|
||||
locations: vec![Pos { line: 1, column: 3 }],
|
||||
path: vec![PathSegment::Field("failure3".to_string())],
|
||||
extensions: Some({
|
||||
let mut values = ErrorExtensionValues::default();
|
||||
values.set("a", 1);
|
||||
values.set("b", 2);
|
||||
values
|
||||
})
|
||||
}]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
schema
|
||||
.execute("{ failure4 }")
|
||||
.await
|
||||
.into_result()
|
||||
.unwrap_err(),
|
||||
vec![ServerError {
|
||||
message: "my error".to_string(),
|
||||
debug_message: Some("MyError".to_string()),
|
||||
locations: vec![Pos { line: 1, column: 3 }],
|
||||
path: vec![PathSegment::Field("failure4".to_string())],
|
||||
extensions: Some({
|
||||
let mut values = ErrorExtensionValues::default();
|
||||
values.set("a", 1);
|
||||
values.set("b", 2);
|
||||
values
|
||||
})
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user