Update error.rs
This commit is contained in:
parent
506e82895f
commit
269f0f0d61
@ -4,6 +4,7 @@ extern crate thiserror;
|
|||||||
use actix_rt;
|
use actix_rt;
|
||||||
use actix_web::{guard, web, App, HttpResponse, HttpServer};
|
use actix_web::{guard, web, App, HttpResponse, HttpServer};
|
||||||
use async_graphql::http::{graphiql_source, playground_source, GQLRequest, GQLResponse};
|
use async_graphql::http::{graphiql_source, playground_source, GQLRequest, GQLResponse};
|
||||||
|
use async_graphql::ErrorExtensions;
|
||||||
use async_graphql::*;
|
use async_graphql::*;
|
||||||
use futures::TryFutureExt;
|
use futures::TryFutureExt;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@ -20,54 +21,87 @@ pub enum MyError {
|
|||||||
ErrorWithoutExtensions,
|
ErrorWithoutExtensions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MyError {
|
impl ErrorExtensions for MyError {
|
||||||
fn extend_err(&self) -> serde_json::Value {
|
// lets define our base extensions
|
||||||
match self {
|
fn extend(&self) -> FieldError {
|
||||||
|
let extensions = match self {
|
||||||
MyError::NotFound => json!({"code": "NOT_FOUND"}),
|
MyError::NotFound => json!({"code": "NOT_FOUND"}),
|
||||||
MyError::ServerError(reason) => json!({ "reason": reason }),
|
MyError::ServerError(reason) => json!({ "reason": reason }),
|
||||||
MyError::ErrorWithoutExtensions => {
|
MyError::ErrorWithoutExtensions => {
|
||||||
json!("This will be ignored since it does not represent an object.")
|
json!("This will be ignored since it does not represent an object.")
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_my_error() -> std::result::Result<String, MyError> {
|
FieldError(format!("{}", self), Some(extensions))
|
||||||
Err(MyError::ServerError("The database is locked".to_owned()))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct QueryRoot;
|
struct QueryRoot;
|
||||||
|
|
||||||
#[Object]
|
#[Object]
|
||||||
impl QueryRoot {
|
impl QueryRoot {
|
||||||
|
// It works on foreign types without extensions as before
|
||||||
#[field]
|
#[field]
|
||||||
async fn do_not_find(&self) -> FieldResult<i32> {
|
async fn parse_without_extensions(&self) -> FieldResult<i32> {
|
||||||
Err(MyError::NotFound).extend_err(MyError::extend_err)
|
Ok("234a".parse()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Foreign types can be extended
|
||||||
#[field]
|
#[field]
|
||||||
async fn fail(&self) -> FieldResult<String> {
|
async fn parse_with_extensions(&self) -> FieldResult<i32> {
|
||||||
Ok(get_my_error().extend_err(MyError::extend_err)?)
|
Ok("234a"
|
||||||
|
.parse()
|
||||||
|
.map_err(|e: std::num::ParseIntError| e.extend_with(|_| json!({"code": 404})))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// THIS does unfortunately NOT work because ErrorExtensions is implemented for &E and not E.
|
||||||
|
// Which is necessary for the overwrite by the user.
|
||||||
|
|
||||||
|
//#[field]
|
||||||
|
// async fn parse_with_extensions_result(&self) -> FieldResult<i32> {
|
||||||
|
// Ok("234a".parse().extend_err(|_| json!({"code": 404}))?)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Using our own types we can implement some base extensions
|
||||||
#[field]
|
#[field]
|
||||||
async fn without_extensions(&self) -> FieldResult<String> {
|
async fn extend(&self) -> FieldResult<i32> {
|
||||||
Err(MyError::ErrorWithoutExtensions).extend_err(MyError::extend_err)?
|
Err(MyError::NotFound.extend())?
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using the ResultExt trait, we can attach extensions on the fly capturing the execution
|
// Or on the result
|
||||||
// environment. This method works on foreign types as well. The trait is implemented for all
|
|
||||||
// Results where the error variant implements `std::error::Error`.
|
|
||||||
#[field]
|
#[field]
|
||||||
async fn parse_value(&self, val: String) -> FieldResult<i32> {
|
async fn extend_result(&self) -> FieldResult<i32> {
|
||||||
val.parse().extend_err(|err| {
|
Err(MyError::NotFound).extend()?
|
||||||
json!({ "description": format!("Could not parse value '{}': {}", val, err) })
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Base extensions can be further extended
|
||||||
#[field]
|
#[field]
|
||||||
async fn parse_value2(&self, val: String) -> FieldResult<i32> {
|
async fn more_extensions(&self) -> FieldResult<String> {
|
||||||
Ok(val.parse()?)
|
// resolves to extensions: { "code": "NOT_FOUND", "reason": "my reason" }
|
||||||
|
Err(MyError::NotFound.extend_with(|_e| json!({"reason": "my reason"})))?
|
||||||
|
}
|
||||||
|
|
||||||
|
// works with results as well
|
||||||
|
#[field]
|
||||||
|
async fn more_extensions_on_result(&self) -> FieldResult<String> {
|
||||||
|
// resolves to extensions: { "code": "NOT_FOUND", "reason": "my reason" }
|
||||||
|
Err(MyError::NotFound).extend_err(|_e| json!({"reason": "my reason"}))?
|
||||||
|
}
|
||||||
|
|
||||||
|
// extend_with is chainable
|
||||||
|
#[field]
|
||||||
|
async fn chainable_extensions(&self) -> FieldResult<String> {
|
||||||
|
let err = MyError::NotFound
|
||||||
|
.extend_with(|_| json!({"ext1": 1}))
|
||||||
|
.extend_with(|_| json!({"ext2": 2}))
|
||||||
|
.extend_with(|_| json!({"ext3": 3}));
|
||||||
|
Err(err)?
|
||||||
|
}
|
||||||
|
|
||||||
|
// extend_with overwrites keys which are already present
|
||||||
|
#[field]
|
||||||
|
async fn overwrite(&self) -> FieldResult<String> {
|
||||||
|
Err(MyError::NotFound.extend_with(|_| json!({"code": "overwritten"})))?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +130,9 @@ async fn gql_graphiql() -> HttpResponse {
|
|||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
|
println!("Playground: http://localhost:8000");
|
||||||
|
println!("Graphiql: http://localhost:8000/graphiql");
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
.data(Schema::new(QueryRoot, EmptyMutation, EmptySubscription))
|
.data(Schema::new(QueryRoot, EmptyMutation, EmptySubscription))
|
||||||
|
77
src/error.rs
77
src/error.rs
@ -1,10 +1,10 @@
|
|||||||
use graphql_parser::query::{ParseError, Value};
|
use graphql_parser::query::{ParseError, Value};
|
||||||
use graphql_parser::Pos;
|
use graphql_parser::Pos;
|
||||||
use std::error::Error as StdError;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
/// FieldError type
|
/// FieldError type
|
||||||
pub struct FieldError(anyhow::Error, Option<serde_json::Value>);
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct FieldError(pub String, pub Option<serde_json::Value>);
|
||||||
|
|
||||||
impl FieldError {
|
impl FieldError {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
@ -37,10 +37,52 @@ pub type FieldResult<T> = std::result::Result<T, FieldError>;
|
|||||||
|
|
||||||
impl<E> From<E> for FieldError
|
impl<E> From<E> for FieldError
|
||||||
where
|
where
|
||||||
E: StdError + Send + Sync + 'static,
|
E: std::fmt::Display + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
fn from(err: E) -> Self {
|
fn from(err: E) -> Self {
|
||||||
FieldError(anyhow::Error::from(err), None)
|
FieldError(format!("{}", err), None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub trait ErrorExtensions
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
fn extend(&self) -> FieldError;
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,26 +90,33 @@ where
|
|||||||
pub trait ResultExt<T, E>
|
pub trait ResultExt<T, E>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
E: StdError + Send + Sync + 'static,
|
|
||||||
{
|
{
|
||||||
fn extend_err<CB>(self, cb: CB) -> FieldResult<T>
|
fn extend_err<CB>(self, cb: CB) -> FieldResult<T>
|
||||||
where
|
where
|
||||||
CB: FnOnce(&E) -> serde_json::Value;
|
CB: FnOnce(&E) -> serde_json::Value;
|
||||||
|
|
||||||
|
fn extend(self) -> FieldResult<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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>
|
impl<T, E> ResultExt<T, E> for std::result::Result<T, E>
|
||||||
where
|
where
|
||||||
E: StdError + Send + Sync + 'static,
|
E: ErrorExtensions + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
fn extend_err<C>(self, cb: C) -> FieldResult<T>
|
fn extend_err<C>(self, cb: C) -> FieldResult<T>
|
||||||
where
|
where
|
||||||
C: FnOnce(&E) -> serde_json::Value,
|
C: FnOnce(&E) -> serde_json::Value,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Err(err) => {
|
Err(err) => Err(err.extend_with(|e| cb(e))),
|
||||||
let extended_err = cb(&err);
|
Ok(value) => Ok(value),
|
||||||
Err(FieldError(err.into(), Some(extended_err)))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extend(self) -> FieldResult<T> {
|
||||||
|
match self {
|
||||||
|
Err(err) => Err(err.extend()),
|
||||||
Ok(value) => Ok(value),
|
Ok(value) => Ok(value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,7 +253,7 @@ pub enum QueryError {
|
|||||||
|
|
||||||
#[error("Failed to resolve field: {err}")]
|
#[error("Failed to resolve field: {err}")]
|
||||||
FieldError {
|
FieldError {
|
||||||
err: anyhow::Error,
|
err: String,
|
||||||
extended_error: Option<serde_json::Value>,
|
extended_error: Option<serde_json::Value>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -229,11 +278,11 @@ pub struct RuleError {
|
|||||||
impl From<ParseError> for Error {
|
impl From<ParseError> for Error {
|
||||||
fn from(err: ParseError) -> Self {
|
fn from(err: ParseError) -> Self {
|
||||||
let msg = err.to_string();
|
let msg = err.to_string();
|
||||||
let mut s = msg.splitn(2, "\n");
|
let mut s = msg.splitn(2, '\n');
|
||||||
let first = s.next().unwrap();
|
let first = s.next().unwrap();
|
||||||
let ln = &first[first.rfind(" ").unwrap() + 1..];
|
let ln = &first[first.rfind(' ').unwrap() + 1..];
|
||||||
let (line, column) = {
|
let (line, column) = {
|
||||||
let mut s = ln.splitn(2, ":");
|
let mut s = ln.splitn(2, ':');
|
||||||
(
|
(
|
||||||
s.next().unwrap().parse().unwrap(),
|
s.next().unwrap().parse().unwrap(),
|
||||||
s.next().unwrap().parse().unwrap(),
|
s.next().unwrap().parse().unwrap(),
|
||||||
|
@ -121,6 +121,7 @@ impl<'a> Serialize for GQLError<'a> {
|
|||||||
if let Some(obj @ serde_json::Value::Object(_)) = extended_error {
|
if let Some(obj @ serde_json::Value::Object(_)) = extended_error {
|
||||||
map.insert("extensions".to_string(), obj.clone());
|
map.insert("extensions".to_string(), obj.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
seq.serialize_element(&serde_json::Value::Object(map))?;
|
seq.serialize_element(&serde_json::Value::Object(map))?;
|
||||||
} else {
|
} else {
|
||||||
seq.serialize_element(&serde_json::json!({
|
seq.serialize_element(&serde_json::json!({
|
||||||
@ -235,7 +236,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
path: None,
|
path: None,
|
||||||
err: QueryError::FieldError {
|
err: QueryError::FieldError {
|
||||||
err: anyhow::anyhow!("MyErrorMessage"),
|
err: "MyErrorMessage".to_owned(),
|
||||||
extended_error: Some(json!({
|
extended_error: Some(json!({
|
||||||
"code": "MY_TEST_CODE"
|
"code": "MY_TEST_CODE"
|
||||||
})),
|
})),
|
||||||
|
@ -100,7 +100,7 @@ pub mod http;
|
|||||||
|
|
||||||
pub use base::{Scalar, Type};
|
pub use base::{Scalar, Type};
|
||||||
pub use context::{Context, QueryPathSegment, Variables};
|
pub use context::{Context, QueryPathSegment, Variables};
|
||||||
pub use error::{Error, FieldError, FieldResult, QueryError, ResultExt};
|
pub use error::{Error, ErrorExtensions, FieldError, FieldResult, QueryError, ResultExt};
|
||||||
pub use graphql_parser::query::Value;
|
pub use graphql_parser::query::Value;
|
||||||
pub use graphql_parser::Pos;
|
pub use graphql_parser::Pos;
|
||||||
pub use query::{QueryBuilder, QueryResponse};
|
pub use query::{QueryBuilder, QueryResponse};
|
||||||
|
Loading…
Reference in New Issue
Block a user