Reimplement the error type and remove the dependency on the anyhow::Error
This commit is contained in:
parent
1d6a892b94
commit
14860d9b88
|
@ -1,5 +1,5 @@
|
|||
use actix_web::{web, App, HttpServer};
|
||||
use async_graphql::{publish, Context, Result, Schema, ID};
|
||||
use async_graphql::{publish, Context, FieldResult, Schema, ID};
|
||||
use futures::lock::Mutex;
|
||||
use slab::Slab;
|
||||
use std::sync::Arc;
|
||||
|
@ -66,7 +66,7 @@ impl MutationRoot {
|
|||
}
|
||||
|
||||
#[field]
|
||||
async fn delete_book(&self, ctx: &Context<'_>, id: ID) -> Result<bool> {
|
||||
async fn delete_book(&self, ctx: &Context<'_>, id: ID) -> FieldResult<bool> {
|
||||
let mut books = ctx.data::<Storage>().lock().await;
|
||||
let id = id.parse::<usize>()?;
|
||||
if books.contains(id) {
|
||||
|
|
|
@ -112,7 +112,7 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
|
|||
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #crate_name::OutputValueType for #ident {
|
||||
async fn resolve(value: &Self, _: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
async fn resolve(value: &Self, _: &#crate_name::ContextSelectionSet<'_>, _pos: #crate_name::Pos) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
#crate_name::EnumType::resolve_enum(value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
|||
None => quote! { || #crate_name::Value::Null },
|
||||
};
|
||||
get_params.push(quote! {
|
||||
let #ident: #ty = ctx.param_value(#name, #param_default)?;
|
||||
let #ident: #ty = ctx.param_value(#name, field.position, #param_default)?;
|
||||
});
|
||||
|
||||
let desc = desc
|
||||
|
@ -205,8 +205,7 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
|||
if field.name.as_str() == #name {
|
||||
#(#get_params)*
|
||||
let ctx_obj = ctx.with_selection_set(&field.selection_set);
|
||||
return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj).await.
|
||||
map_err(|err| err.with_position(field.position).into());
|
||||
return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj, field.position).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -251,34 +250,32 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
|||
#[#crate_name::async_trait::async_trait]
|
||||
impl #generics #crate_name::ObjectType for #ident #generics {
|
||||
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>, field: &#crate_name::graphql_parser::query::Field) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
use #crate_name::ErrorWithPosition;
|
||||
|
||||
#(#resolvers)*
|
||||
|
||||
#crate_name::anyhow::bail!(#crate_name::QueryError::FieldNotFound {
|
||||
Err(#crate_name::QueryError::FieldNotFound {
|
||||
field_name: field.name.clone(),
|
||||
object: #gql_typename.to_string(),
|
||||
}
|
||||
.with_position(field.position));
|
||||
}.into_error(field.position))
|
||||
}
|
||||
|
||||
fn collect_inline_fields<'a>(
|
||||
&'a self,
|
||||
name: &str,
|
||||
pos: #crate_name::Pos,
|
||||
ctx: &#crate_name::ContextSelectionSet<'a>,
|
||||
futures: &mut Vec<#crate_name::BoxFieldFuture<'a>>,
|
||||
) -> #crate_name::Result<()> {
|
||||
#(#collect_inline_fields)*
|
||||
#crate_name::anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment {
|
||||
Err(#crate_name::QueryError::UnrecognizedInlineFragment {
|
||||
object: #gql_typename.to_string(),
|
||||
name: name.to_string(),
|
||||
});
|
||||
}.into_error(pos))
|
||||
}
|
||||
}
|
||||
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #generics #crate_name::OutputValueType for #ident #generics {
|
||||
async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>, pos: #crate_name::Pos) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
#crate_name::do_resolve(ctx, value).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -174,7 +174,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
|||
};
|
||||
|
||||
get_params.push(quote! {
|
||||
let #ident: #ty = ctx.param_value(#name, #default)?;
|
||||
let #ident: #ty = ctx.param_value(#name, field.position, #default)?;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
|||
});
|
||||
});
|
||||
|
||||
let ctx_field = if arg_ctx {
|
||||
let ctx_param = if arg_ctx {
|
||||
quote! { &ctx, }
|
||||
} else {
|
||||
quote! {}
|
||||
|
@ -204,12 +204,14 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
|||
let field_ident = &method.sig.ident;
|
||||
let resolve_obj = match &ty {
|
||||
OutputType::Value(_) => quote! {
|
||||
self.#field_ident(#ctx_field #(#use_params),*).await
|
||||
self.#field_ident(#ctx_param #(#use_params),*).await
|
||||
},
|
||||
OutputType::Result(_, _) => {
|
||||
quote! {
|
||||
self.#field_ident(#ctx_field #(#use_params),*).await.
|
||||
map_err(|err| err.with_position(field.position))?
|
||||
{
|
||||
let res:#crate_name::FieldResult<_> = self.#field_ident(#ctx_param #(#use_params),*).await;
|
||||
res.map_err(|err| err.into_error_with_path(field.position, ctx.path_node.as_ref().unwrap().to_json()))?
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -218,8 +220,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
|||
if field.name.as_str() == #field_name {
|
||||
#(#get_params)*
|
||||
let ctx_obj = ctx.with_selection_set(&field.selection_set);
|
||||
return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj).await.
|
||||
map_err(|err| err.with_position(field.position).into());
|
||||
return #crate_name::OutputValueType::resolve(&#resolve_obj, &ctx_obj, field.position).await;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -272,21 +273,17 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
|||
#[#crate_name::async_trait::async_trait]
|
||||
impl#generics #crate_name::ObjectType for #self_ty {
|
||||
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>, field: &#crate_name::graphql_parser::query::Field) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
use #crate_name::ErrorWithPosition;
|
||||
|
||||
#(#resolvers)*
|
||||
|
||||
#crate_name::anyhow::bail!(#crate_name::QueryError::FieldNotFound {
|
||||
Err(#crate_name::QueryError::FieldNotFound {
|
||||
field_name: field.name.clone(),
|
||||
object: #gql_typename.to_string(),
|
||||
}
|
||||
.with_position(field.position));
|
||||
}.into_error(field.position))
|
||||
}
|
||||
}
|
||||
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #generics #crate_name::OutputValueType for #self_ty {
|
||||
async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>, pos: #crate_name::Pos) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
#crate_name::do_resolve(ctx, value).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@ pub enum OutputType<'a> {
|
|||
impl<'a> OutputType<'a> {
|
||||
pub fn parse(input: &'a Type) -> Result<Self> {
|
||||
let ty = if let Type::Path(p) = input {
|
||||
if p.path.segments.last().unwrap().ident == "Result" {
|
||||
if p.path.segments.last().unwrap().ident == "Result"
|
||||
|| p.path.segments.last().unwrap().ident == "FieldResult"
|
||||
{
|
||||
if let PathArguments::AngleBracketed(args) = &p.path.segments[0].arguments {
|
||||
if args.args.is_empty() {
|
||||
return Err(Error::new_spanned(input, "Invalid type"));
|
||||
|
|
|
@ -76,8 +76,7 @@ pub fn generate(object_args: &args::Object, input: &mut DeriveInput) -> Result<T
|
|||
resolvers.push(quote! {
|
||||
if field.name.as_str() == #field_name {
|
||||
let ctx_obj = ctx.with_selection_set(&field.selection_set);
|
||||
return #crate_name::OutputValueType::resolve(&self.#ident, &ctx_obj).await.
|
||||
map_err(|err| err.with_position(field.position).into());
|
||||
return #crate_name::OutputValueType::resolve(&self.#ident, &ctx_obj, field.position).await;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -128,21 +127,18 @@ pub fn generate(object_args: &args::Object, input: &mut DeriveInput) -> Result<T
|
|||
#[#crate_name::async_trait::async_trait]
|
||||
impl #generics #crate_name::ObjectType for #ident #generics {
|
||||
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>, field: &#crate_name::graphql_parser::query::Field) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
use #crate_name::ErrorWithPosition;
|
||||
|
||||
#(#resolvers)*
|
||||
|
||||
#crate_name::anyhow::bail!(#crate_name::QueryError::FieldNotFound {
|
||||
Err(#crate_name::QueryError::FieldNotFound {
|
||||
field_name: field.name.clone(),
|
||||
object: #gql_typename.to_string(),
|
||||
}
|
||||
.with_position(field.position));
|
||||
}.into_error(field.position))
|
||||
}
|
||||
}
|
||||
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #generics #crate_name::OutputValueType for #ident #generics {
|
||||
async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>, _pos: #crate_name::Pos) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
#crate_name::do_resolve(ctx, value).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,7 +181,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
|||
};
|
||||
|
||||
get_params.push(quote! {
|
||||
let #ident: #ty = ctx_field.param_value(#name, #default)?;
|
||||
let #ident: #ty = ctx_field.param_value(#name, field.position, #default)?;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -213,7 +213,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
|||
if self.#ident(msg, #(#use_params)*) {
|
||||
let ctx_selection_set = ctx_field.with_selection_set(&field.selection_set);
|
||||
let value =
|
||||
#crate_name::OutputValueType::resolve(msg, &ctx_selection_set).await?;
|
||||
#crate_name::OutputValueType::resolve(msg, &ctx_selection_set, field.position).await?;
|
||||
let mut res = #crate_name::serde_json::Map::new();
|
||||
res.insert(ctx_field.result_name().to_string(), value);
|
||||
return Ok(Some(res.into()));
|
||||
|
@ -251,13 +251,11 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
|||
#[#crate_name::async_trait::async_trait]
|
||||
impl #crate_name::SubscriptionType for SubscriptionRoot {
|
||||
fn create_type(field: &#crate_name::graphql_parser::query::Field, types: &mut std::collections::HashMap<std::any::TypeId, #crate_name::graphql_parser::query::Field>) -> #crate_name::Result<()> {
|
||||
use #crate_name::ErrorWithPosition;
|
||||
#(#create_types)*
|
||||
#crate_name::anyhow::bail!(#crate_name::QueryError::FieldNotFound {
|
||||
Err(#crate_name::QueryError::FieldNotFound {
|
||||
field_name: field.name.clone(),
|
||||
object: #gql_typename.to_string(),
|
||||
}
|
||||
.with_position(field.position));
|
||||
}.into_error(field.position))
|
||||
}
|
||||
|
||||
async fn resolve(
|
||||
|
|
|
@ -98,31 +98,30 @@ pub fn generate(interface_args: &args::Interface, input: &DeriveInput) -> Result
|
|||
#[#crate_name::async_trait::async_trait]
|
||||
impl #generics #crate_name::ObjectType for #ident #generics {
|
||||
async fn resolve_field(&self, ctx: &#crate_name::Context<'_>, field: &#crate_name::graphql_parser::query::Field) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
use #crate_name::ErrorWithPosition;
|
||||
anyhow::bail!(#crate_name::QueryError::FieldNotFound {
|
||||
Err(#crate_name::QueryError::FieldNotFound {
|
||||
field_name: field.name.clone(),
|
||||
object: #gql_typename.to_string(),
|
||||
}
|
||||
.with_position(field.position));
|
||||
}.into_error(field.position))
|
||||
}
|
||||
|
||||
fn collect_inline_fields<'a>(
|
||||
&'a self,
|
||||
name: &str,
|
||||
pos: #crate_name::Pos,
|
||||
ctx: &#crate_name::ContextSelectionSet<'a>,
|
||||
futures: &mut Vec<#crate_name::BoxFieldFuture<'a>>,
|
||||
) -> #crate_name::Result<()> {
|
||||
#(#collect_inline_fields)*
|
||||
#crate_name::anyhow::bail!(#crate_name::QueryError::UnrecognizedInlineFragment {
|
||||
Err(#crate_name::QueryError::UnrecognizedInlineFragment {
|
||||
object: #gql_typename.to_string(),
|
||||
name: name.to_string(),
|
||||
});
|
||||
}.into_error(pos))
|
||||
}
|
||||
}
|
||||
|
||||
#[#crate_name::async_trait::async_trait]
|
||||
impl #generics #crate_name::OutputValueType for #ident #generics {
|
||||
async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
async fn resolve(value: &Self, ctx: &#crate_name::ContextSelectionSet<'_>, pos: #crate_name::Pos) -> #crate_name::Result<#crate_name::serde_json::Value> {
|
||||
#crate_name::do_resolve(ctx, value).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#[macro_use]
|
||||
extern crate thiserror;
|
||||
|
||||
use actix_rt;
|
||||
use actix_web::{guard, web, App, HttpResponse, HttpServer};
|
||||
use async_graphql::http::{graphiql_source, playground_source, GQLRequest, GQLResponse};
|
||||
|
@ -5,34 +8,26 @@ use async_graphql::*;
|
|||
use futures::TryFutureExt;
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MyError {
|
||||
#[error("Could not find resource")]
|
||||
NotFound,
|
||||
|
||||
#[error("ServerError")]
|
||||
ServerError(String),
|
||||
|
||||
#[error("No Extensions")]
|
||||
ErrorWithoutExtensions,
|
||||
}
|
||||
|
||||
// Let's implement a mapping from our MyError to async_graphql::Error (anyhow::Error).
|
||||
// But instead of mapping to async_graphql::Error directly we map to async_graphql::ExtendedError,
|
||||
// which gives us the opportunity to provide custom extensions to our errors.
|
||||
// Note: Values which can't get serialized to JSON-Objects simply get ignored.
|
||||
impl From<MyError> for Error {
|
||||
fn from(my_error: MyError) -> Error {
|
||||
match my_error {
|
||||
MyError::NotFound => {
|
||||
let msg = "Could not find resource".to_owned();
|
||||
let extensions = json!({"code": "NOT_FOUND"});
|
||||
ExtendedError(msg, extensions).into()
|
||||
impl MyError {
|
||||
fn extend_err(&self) -> serde_json::Value {
|
||||
match self {
|
||||
MyError::NotFound => json!({"code": "NOT_FOUND"}),
|
||||
MyError::ServerError(reason) => json!({ "reason": reason }),
|
||||
MyError::ErrorWithoutExtensions => {
|
||||
json!("This will be ignored since it does not represent an object.")
|
||||
}
|
||||
MyError::ServerError(reason) => {
|
||||
ExtendedError("ServerError".to_owned(), json!({ "reason": reason })).into()
|
||||
}
|
||||
|
||||
MyError::ErrorWithoutExtensions => ExtendedError(
|
||||
"No Extensions".to_owned(),
|
||||
json!("This will be ignored since it does not represent an object."),
|
||||
)
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,34 +36,39 @@ fn get_my_error() -> std::result::Result<String, MyError> {
|
|||
Err(MyError::ServerError("The database is locked".to_owned()))
|
||||
}
|
||||
|
||||
struct QueryRoot {}
|
||||
struct QueryRoot;
|
||||
|
||||
#[Object]
|
||||
impl QueryRoot {
|
||||
#[field]
|
||||
async fn do_not_find(&self) -> Result<i32> {
|
||||
Err(MyError::NotFound)?
|
||||
async fn do_not_find(&self) -> FieldResult<i32> {
|
||||
Err(MyError::NotFound).extend_err(MyError::extend_err)
|
||||
}
|
||||
|
||||
#[field]
|
||||
async fn fail(&self) -> Result<String> {
|
||||
Ok(get_my_error()?)
|
||||
async fn fail(&self) -> FieldResult<String> {
|
||||
Ok(get_my_error().extend_err(MyError::extend_err)?)
|
||||
}
|
||||
|
||||
#[field]
|
||||
async fn without_extensions(&self) -> Result<String> {
|
||||
Err(MyError::ErrorWithoutExtensions)?
|
||||
async fn without_extensions(&self) -> FieldResult<String> {
|
||||
Err(MyError::ErrorWithoutExtensions).extend_err(MyError::extend_err)?
|
||||
}
|
||||
|
||||
// Using the ResultExt trait, we can attach extensions on the fly capturing the execution
|
||||
// environment. This method works on foreign types as well. The trait is implemented for all
|
||||
// Results where the error variant implements Display.
|
||||
// Results where the error variant implements `std::error::Error`.
|
||||
#[field]
|
||||
async fn parse_value(&self, val: String) -> Result<i32> {
|
||||
async fn parse_value(&self, val: String) -> FieldResult<i32> {
|
||||
val.parse().extend_err(|err| {
|
||||
json!({ "description": format!("Could not parse value {}: {}", val, err) })
|
||||
json!({ "description": format!("Could not parse value '{}': {}", val, err) })
|
||||
})
|
||||
}
|
||||
|
||||
#[field]
|
||||
async fn parse_value2(&self, val: String) -> FieldResult<i32> {
|
||||
Ok(val.parse()?)
|
||||
}
|
||||
}
|
||||
|
||||
async fn index(
|
||||
|
@ -99,7 +99,7 @@ async fn gql_graphiql() -> HttpResponse {
|
|||
async fn main() -> std::io::Result<()> {
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.data(Schema::new(QueryRoot {}, EmptyMutation, EmptySubscription))
|
||||
.data(Schema::new(QueryRoot, EmptyMutation, EmptySubscription))
|
||||
.service(web::resource("/").guard(guard::Post()).to(index))
|
||||
.service(web::resource("/").guard(guard::Get()).to(gql_playgound))
|
||||
.service(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::StarWars;
|
||||
use async_graphql::{Connection, Context, DataSource, EmptyEdgeFields, Result};
|
||||
use async_graphql::{Connection, Context, DataSource, EmptyEdgeFields, FieldResult};
|
||||
|
||||
#[async_graphql::Enum(desc = "One of the films in the Star Wars Trilogy")]
|
||||
pub enum Episode {
|
||||
|
@ -118,7 +118,7 @@ impl QueryRoot {
|
|||
before: Option<String>,
|
||||
first: Option<i32>,
|
||||
last: Option<i32>,
|
||||
) -> Result<Connection<Human, EmptyEdgeFields>> {
|
||||
) -> FieldResult<Connection<Human, EmptyEdgeFields>> {
|
||||
let humans = ctx
|
||||
.data::<StarWars>()
|
||||
.humans()
|
||||
|
@ -149,7 +149,7 @@ impl QueryRoot {
|
|||
before: Option<String>,
|
||||
first: Option<i32>,
|
||||
last: Option<i32>,
|
||||
) -> Result<Connection<Droid, EmptyEdgeFields>> {
|
||||
) -> FieldResult<Connection<Droid, EmptyEdgeFields>> {
|
||||
let droids = ctx
|
||||
.data::<StarWars>()
|
||||
.droids()
|
||||
|
|
44
src/base.rs
44
src/base.rs
|
@ -1,6 +1,7 @@
|
|||
use crate::registry::Registry;
|
||||
use crate::{registry, Context, ContextSelectionSet, QueryError, Result, ID};
|
||||
use crate::{registry, Context, ContextSelectionSet, Error, QueryError, Result, ID};
|
||||
use graphql_parser::query::{Field, Value};
|
||||
use graphql_parser::Pos;
|
||||
use std::borrow::Cow;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
@ -26,19 +27,15 @@ pub trait Type {
|
|||
}
|
||||
|
||||
/// Parse `GlobalID`.
|
||||
fn from_global_id(id: ID) -> Result<ID> {
|
||||
fn from_global_id(id: ID) -> Option<ID> {
|
||||
let v: Vec<&str> = id.splitn(2, ':').collect();
|
||||
if v.len() != 2 {
|
||||
return Err(QueryError::InvalidGlobalID.into());
|
||||
return None;
|
||||
}
|
||||
if v[0] != Self::type_name() {
|
||||
return Err(QueryError::InvalidGlobalIDType {
|
||||
expect: Self::type_name().to_string(),
|
||||
actual: v[0].to_string(),
|
||||
}
|
||||
.into());
|
||||
return None;
|
||||
}
|
||||
Ok(v[1].to_string().into())
|
||||
Some(v[1].to_string().into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,7 +49,11 @@ pub trait InputValueType: Type + Sized {
|
|||
#[async_trait::async_trait]
|
||||
pub trait OutputValueType: Type {
|
||||
/// Resolve an output value to `serde_json::Value`.
|
||||
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value>;
|
||||
async fn resolve(
|
||||
value: &Self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
pos: Pos,
|
||||
) -> Result<serde_json::Value>;
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
|
@ -75,13 +76,18 @@ pub trait ObjectType: OutputValueType {
|
|||
fn collect_inline_fields<'a>(
|
||||
&'a self,
|
||||
name: &str,
|
||||
pos: Pos,
|
||||
_ctx: &ContextSelectionSet<'a>,
|
||||
_futures: &mut Vec<BoxFieldFuture<'a>>,
|
||||
) -> Result<()> {
|
||||
anyhow::bail!(QueryError::UnrecognizedInlineFragment {
|
||||
object: Self::type_name().to_string(),
|
||||
name: name.to_string(),
|
||||
});
|
||||
Err(Error::Query {
|
||||
pos,
|
||||
path: None,
|
||||
err: QueryError::UnrecognizedInlineFragment {
|
||||
object: Self::type_name().to_string(),
|
||||
name: name.to_string(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,6 +178,7 @@ macro_rules! impl_scalar_internal {
|
|||
async fn resolve(
|
||||
value: &Self,
|
||||
_: &crate::ContextSelectionSet<'_>,
|
||||
_pos: crate::Pos,
|
||||
) -> crate::Result<serde_json::Value> {
|
||||
value.to_json()
|
||||
}
|
||||
|
@ -209,6 +216,7 @@ macro_rules! impl_scalar {
|
|||
async fn resolve(
|
||||
value: &Self,
|
||||
_: &async_graphql::ContextSelectionSet<'_>,
|
||||
_pos: async_graphql::Pos,
|
||||
) -> async_graphql::Result<serde_json::Value> {
|
||||
value.to_json()
|
||||
}
|
||||
|
@ -229,7 +237,11 @@ impl<T: Type + Send + Sync> Type for &T {
|
|||
#[async_trait::async_trait]
|
||||
impl<T: OutputValueType + Send + Sync> OutputValueType for &T {
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
T::resolve(*value, ctx).await
|
||||
async fn resolve(
|
||||
value: &Self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
pos: Pos,
|
||||
) -> Result<serde_json::Value> {
|
||||
T::resolve(*value, ctx, pos).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::extensions::BoxExtension;
|
||||
use crate::registry::Registry;
|
||||
use crate::{ErrorWithPosition, InputValueType, QueryError, Result, Type};
|
||||
use crate::{InputValueType, Pos, QueryError, Result, Type};
|
||||
use bytes::Bytes;
|
||||
use graphql_parser::query::{
|
||||
Directive, Field, FragmentDefinition, SelectionSet, Value, VariableDefinition,
|
||||
|
@ -205,12 +205,25 @@ impl<'a> QueryPathNode<'a> {
|
|||
}
|
||||
f(&self.segment);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn to_json(&self) -> serde_json::Value {
|
||||
let mut path: Vec<serde_json::Value> = Vec::new();
|
||||
self.for_each(|segment| {
|
||||
path.push(match segment {
|
||||
QueryPathSegment::Index(idx) => (*idx).into(),
|
||||
QueryPathSegment::Name(name) => (*name).to_string().into(),
|
||||
})
|
||||
});
|
||||
path.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Query context
|
||||
#[derive(Clone)]
|
||||
pub struct ContextBase<'a, T> {
|
||||
pub(crate) path_node: Option<QueryPathNode<'a>>,
|
||||
#[allow(missing_docs)]
|
||||
pub path_node: Option<QueryPathNode<'a>>,
|
||||
pub(crate) resolve_id: &'a AtomicUsize,
|
||||
pub(crate) extensions: &'a [BoxExtension],
|
||||
pub(crate) item: T,
|
||||
|
@ -289,7 +302,7 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
.expect("The specified data type does not exist.")
|
||||
}
|
||||
|
||||
fn var_value(&self, name: &str) -> Result<Value> {
|
||||
fn var_value(&self, name: &str, pos: Pos) -> Result<Value> {
|
||||
let def = self
|
||||
.variable_definitions
|
||||
.iter()
|
||||
|
@ -304,16 +317,16 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
Err(QueryError::VarNotDefined {
|
||||
var_name: name.to_string(),
|
||||
}
|
||||
.into())
|
||||
.into_error(pos))
|
||||
}
|
||||
|
||||
fn resolve_input_value(&self, mut value: Value) -> Result<Value> {
|
||||
fn resolve_input_value(&self, mut value: Value, pos: Pos) -> Result<Value> {
|
||||
match value {
|
||||
Value::Variable(var_name) => self.var_value(&var_name),
|
||||
Value::Variable(var_name) => self.var_value(&var_name, pos),
|
||||
Value::List(ref mut ls) => {
|
||||
for value in ls {
|
||||
if let Value::Variable(var_name) = value {
|
||||
*value = self.var_value(&var_name)?;
|
||||
*value = self.var_value(&var_name, pos)?;
|
||||
}
|
||||
}
|
||||
Ok(value)
|
||||
|
@ -321,7 +334,7 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
Value::Object(ref mut obj) => {
|
||||
for value in obj.values_mut() {
|
||||
if let Value::Variable(var_name) = value {
|
||||
*value = self.var_value(&var_name)?;
|
||||
*value = self.var_value(&var_name, pos)?;
|
||||
}
|
||||
}
|
||||
Ok(value)
|
||||
|
@ -340,13 +353,13 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
.find(|(name, _)| name == "if")
|
||||
.map(|(_, value)| value)
|
||||
{
|
||||
let value = self.resolve_input_value(value.clone())?;
|
||||
let value = self.resolve_input_value(value.clone(), directive.position)?;
|
||||
let res: bool = InputValueType::parse(&value).ok_or_else(|| {
|
||||
QueryError::ExpectedType {
|
||||
expect: bool::qualified_type_name(),
|
||||
actual: value,
|
||||
}
|
||||
.with_position(directive.position)
|
||||
.into_error(directive.position)
|
||||
})?;
|
||||
if res {
|
||||
return Ok(true);
|
||||
|
@ -357,8 +370,7 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
arg_name: "if",
|
||||
arg_type: "Boolean!",
|
||||
}
|
||||
.with_position(directive.position)
|
||||
.into());
|
||||
.into_error(directive.position));
|
||||
}
|
||||
} else if directive.name == "include" {
|
||||
if let Some(value) = directive
|
||||
|
@ -367,13 +379,13 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
.find(|(name, _)| name == "if")
|
||||
.map(|(_, value)| value)
|
||||
{
|
||||
let value = self.resolve_input_value(value.clone())?;
|
||||
let value = self.resolve_input_value(value.clone(), directive.position)?;
|
||||
let res: bool = InputValueType::parse(&value).ok_or_else(|| {
|
||||
QueryError::ExpectedType {
|
||||
expect: bool::qualified_type_name(),
|
||||
actual: value,
|
||||
}
|
||||
.with_position(directive.position)
|
||||
.into_error(directive.position)
|
||||
})?;
|
||||
if !res {
|
||||
return Ok(true);
|
||||
|
@ -384,15 +396,13 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
arg_name: "if",
|
||||
arg_type: "Boolean!",
|
||||
}
|
||||
.with_position(directive.position)
|
||||
.into());
|
||||
.into_error(directive.position));
|
||||
}
|
||||
} else {
|
||||
return Err(QueryError::UnknownDirective {
|
||||
name: directive.name.clone(),
|
||||
}
|
||||
.with_position(directive.position)
|
||||
.into());
|
||||
.into_error(directive.position));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -426,6 +436,7 @@ impl<'a> ContextBase<'a, &'a Field> {
|
|||
pub fn param_value<T: InputValueType, F: FnOnce() -> Value>(
|
||||
&self,
|
||||
name: &str,
|
||||
pos: Pos,
|
||||
default: F,
|
||||
) -> Result<T> {
|
||||
match self
|
||||
|
@ -436,13 +447,13 @@ impl<'a> ContextBase<'a, &'a Field> {
|
|||
.cloned()
|
||||
{
|
||||
Some(value) => {
|
||||
let value = self.resolve_input_value(value)?;
|
||||
let value = self.resolve_input_value(value, pos)?;
|
||||
let res = InputValueType::parse(&value).ok_or_else(|| {
|
||||
QueryError::ExpectedType {
|
||||
expect: T::qualified_type_name(),
|
||||
actual: value,
|
||||
}
|
||||
.with_position(self.item.position)
|
||||
.into_error(pos)
|
||||
})?;
|
||||
Ok(res)
|
||||
}
|
||||
|
@ -453,7 +464,7 @@ impl<'a> ContextBase<'a, &'a Field> {
|
|||
expect: T::qualified_type_name(),
|
||||
actual: value.clone(),
|
||||
}
|
||||
.with_position(self.item.position)
|
||||
.into_error(pos)
|
||||
})?;
|
||||
Ok(res)
|
||||
}
|
||||
|
|
214
src/error.rs
214
src/error.rs
|
@ -1,12 +1,77 @@
|
|||
use crate::Error;
|
||||
use graphql_parser::query::Value;
|
||||
use graphql_parser::query::{ParseError, Value};
|
||||
use graphql_parser::Pos;
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// Error for query parser
|
||||
#[derive(Debug, Error)]
|
||||
#[error("{0}")]
|
||||
pub struct QueryParseError(pub(crate) String);
|
||||
/// FieldError type
|
||||
pub struct FieldError(anyhow::Error, 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn into_error_with_path(self, pos: Pos, path: serde_json::Value) -> Error {
|
||||
Error::Query {
|
||||
pos,
|
||||
path: Some(path),
|
||||
err: QueryError::FieldError {
|
||||
err: self.0,
|
||||
extended_error: self.1,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// FieldResult type
|
||||
pub type FieldResult<T> = std::result::Result<T, FieldError>;
|
||||
|
||||
impl<E> From<E> for FieldError
|
||||
where
|
||||
E: StdError + Send + Sync + 'static,
|
||||
{
|
||||
fn from(err: E) -> Self {
|
||||
FieldError(anyhow::Error::from(err), None)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub trait ResultExt<T, E>
|
||||
where
|
||||
Self: Sized,
|
||||
E: StdError + Send + Sync + 'static,
|
||||
{
|
||||
fn extend_err<CB>(self, cb: CB) -> FieldResult<T>
|
||||
where
|
||||
CB: FnOnce(&E) -> serde_json::Value;
|
||||
}
|
||||
|
||||
impl<T, E> ResultExt<T, E> for std::result::Result<T, E>
|
||||
where
|
||||
E: StdError + Send + Sync + 'static,
|
||||
{
|
||||
fn extend_err<C>(self, cb: C) -> FieldResult<T>
|
||||
where
|
||||
C: FnOnce(&E) -> serde_json::Value,
|
||||
{
|
||||
match self {
|
||||
Err(err) => {
|
||||
let extended_err = cb(&err);
|
||||
Err(FieldError(err.into(), Some(extended_err)))
|
||||
}
|
||||
Ok(value) => Ok(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for query
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -131,128 +196,57 @@ pub enum QueryError {
|
|||
name: String,
|
||||
},
|
||||
|
||||
#[error("Argument \"{field_name}\" must be a non-negative integer")]
|
||||
ArgumentMustBeNonNegative {
|
||||
/// Field name
|
||||
field_name: String,
|
||||
},
|
||||
|
||||
#[error("Invalid global id")]
|
||||
InvalidGlobalID,
|
||||
|
||||
#[error("Invalid global id, expected type \"{expect}\", found {actual}.")]
|
||||
InvalidGlobalIDType {
|
||||
/// Expect type
|
||||
expect: String,
|
||||
|
||||
/// Actual type
|
||||
actual: String,
|
||||
},
|
||||
|
||||
#[error("Too complex.")]
|
||||
#[error("Too complex")]
|
||||
TooComplex,
|
||||
|
||||
#[error("Too deep.")]
|
||||
#[error("Too deep")]
|
||||
TooDeep,
|
||||
|
||||
#[error("Failed to resolve field: {err}")]
|
||||
FieldError {
|
||||
err: anyhow::Error,
|
||||
extended_error: Option<serde_json::Value>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Creates a wrapper with an error location
|
||||
#[allow(missing_docs)]
|
||||
pub trait ErrorWithPosition {
|
||||
type Result;
|
||||
|
||||
fn with_position(self, position: Pos) -> PositionError;
|
||||
}
|
||||
|
||||
impl<T: Into<Error>> ErrorWithPosition for T {
|
||||
type Result = PositionError;
|
||||
|
||||
fn with_position(self, position: Pos) -> PositionError {
|
||||
PositionError {
|
||||
position,
|
||||
inner: self.into(),
|
||||
impl QueryError {
|
||||
#[doc(hidden)]
|
||||
pub fn into_error(self, pos: Pos) -> Error {
|
||||
Error::Query {
|
||||
pos,
|
||||
path: None,
|
||||
err: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper with the wrong location
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Error)]
|
||||
pub struct PositionError {
|
||||
pub position: Pos,
|
||||
pub inner: Error,
|
||||
}
|
||||
|
||||
impl PositionError {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new(position: Pos, inner: Error) -> Self {
|
||||
Self { position, inner }
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub fn into_inner(self) -> Error {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PositionError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RuleError {
|
||||
pub locations: Vec<Pos>,
|
||||
pub message: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub struct RuleErrors {
|
||||
pub errors: Vec<RuleError>,
|
||||
}
|
||||
|
||||
impl Display for RuleErrors {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
for error in &self.errors {
|
||||
writeln!(f, "{}", error.message)?;
|
||||
impl From<ParseError> for Error {
|
||||
fn from(err: ParseError) -> Self {
|
||||
Error::Parse {
|
||||
message: err.to_string(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapped Error with extensions.
|
||||
#[derive(Debug, Error)]
|
||||
pub struct ExtendedError(pub String, pub serde_json::Value);
|
||||
|
||||
impl Display for ExtendedError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub trait ResultExt<T, E>
|
||||
where
|
||||
Self: Sized,
|
||||
E: std::fmt::Display + Sized,
|
||||
{
|
||||
fn extend_err<CB>(self, cb: CB) -> crate::Result<T>
|
||||
where
|
||||
CB: FnOnce(&E) -> serde_json::Value;
|
||||
}
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Parse error: {message}")]
|
||||
Parse { message: String },
|
||||
|
||||
impl<T, E> ResultExt<T, E> for std::result::Result<T, E>
|
||||
where
|
||||
E: std::fmt::Display + Sized,
|
||||
{
|
||||
fn extend_err<C>(self, cb: C) -> crate::Result<T>
|
||||
where
|
||||
C: FnOnce(&E) -> serde_json::Value,
|
||||
{
|
||||
match self {
|
||||
Err(e) => Err(anyhow::anyhow!(ExtendedError(e.to_string(), cb(&e)))),
|
||||
Ok(value) => Ok(value),
|
||||
}
|
||||
}
|
||||
#[error("Query error: {err}")]
|
||||
Query {
|
||||
pos: Pos,
|
||||
path: Option<serde_json::Value>,
|
||||
err: QueryError,
|
||||
},
|
||||
|
||||
#[error("Rule error")]
|
||||
Rule { errors: Vec<RuleError> },
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use crate::extensions::{Extension, ResolveInfo};
|
||||
use crate::QueryPathSegment;
|
||||
use chrono::{DateTime, Utc};
|
||||
use parking_lot::Mutex;
|
||||
use serde::ser::SerializeMap;
|
||||
|
@ -91,16 +90,7 @@ impl Extension for ApolloTracing {
|
|||
inner.pending_resolves.insert(
|
||||
info.resolve_id,
|
||||
PendingResolve {
|
||||
path: {
|
||||
let mut path: Vec<serde_json::Value> = Vec::new();
|
||||
info.path_node.for_each(|segment| {
|
||||
path.push(match segment {
|
||||
QueryPathSegment::Index(idx) => (*idx).into(),
|
||||
QueryPathSegment::Name(name) => (*name).to_string().into(),
|
||||
})
|
||||
});
|
||||
path.into()
|
||||
},
|
||||
path: info.path_node.to_json(),
|
||||
field_name: info.path_node.field_name().to_string(),
|
||||
parent_type: info.parent_type.to_string(),
|
||||
return_type: info.return_type.to_string(),
|
||||
|
|
186
src/http/mod.rs
186
src/http/mod.rs
|
@ -6,15 +6,13 @@ mod playground_source;
|
|||
pub use graphiql_source::graphiql_source;
|
||||
pub use playground_source::playground_source;
|
||||
|
||||
use crate::error::{ExtendedError, RuleError, RuleErrors};
|
||||
use crate::{
|
||||
ObjectType, PositionError, QueryBuilder, QueryResult, Result, Schema, SubscriptionType,
|
||||
Error, ObjectType, QueryBuilder, QueryError, QueryResponse, Result, Schema, SubscriptionType,
|
||||
Variables,
|
||||
};
|
||||
use graphql_parser::Pos;
|
||||
use serde::ser::{SerializeMap, SerializeSeq};
|
||||
use serde::{Serialize, Serializer};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// GraphQL Request object
|
||||
#[derive(Deserialize, Clone, PartialEq, Debug)]
|
||||
|
@ -55,7 +53,7 @@ impl GQLRequest {
|
|||
}
|
||||
|
||||
/// Serializable query result type
|
||||
pub struct GQLResponse(pub Result<QueryResult>);
|
||||
pub struct GQLResponse(pub Result<QueryResponse>);
|
||||
|
||||
impl Serialize for GQLResponse {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
|
||||
|
@ -81,45 +79,62 @@ impl Serialize for GQLResponse {
|
|||
}
|
||||
|
||||
/// Serializable error type
|
||||
pub struct GQLError<'a>(pub &'a anyhow::Error);
|
||||
|
||||
impl<'a> Deref for GQLError<'a> {
|
||||
type Target = anyhow::Error;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
pub struct GQLError<'a>(pub &'a Error);
|
||||
|
||||
impl<'a> Serialize for GQLError<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if let Some(err) = self.0.downcast_ref::<PositionError>() {
|
||||
let mut seq = serializer.serialize_seq(Some(1))?;
|
||||
seq.serialize_element(&GQLPositionError(err))?;
|
||||
seq.end()
|
||||
} else if let Some(err) = self.0.downcast_ref::<RuleErrors>() {
|
||||
let mut seq = serializer.serialize_seq(Some(err.errors.len()))?;
|
||||
for err in &err.errors {
|
||||
seq.serialize_element(&GQLRuleError(err))?;
|
||||
}
|
||||
seq.end()
|
||||
} else {
|
||||
let mut seq = serializer.serialize_seq(None)?;
|
||||
if let Some(extensions) = get_error_extensions(self.0) {
|
||||
seq.serialize_element(&serde_json::json!({
|
||||
"message": self.0.to_string(),
|
||||
"extensions": extensions
|
||||
}))?;
|
||||
} else {
|
||||
seq.serialize_element(&serde_json::json!({
|
||||
"message": self.0.to_string(),
|
||||
match self.0 {
|
||||
Error::Parse { message } => {
|
||||
let mut seq = serializer.serialize_seq(Some(1))?;
|
||||
seq.serialize_element(&serde_json::json! ({
|
||||
"message": message,
|
||||
}))?;
|
||||
seq.end()
|
||||
}
|
||||
Error::Query { pos, path, err } => {
|
||||
let mut seq = serializer.serialize_seq(Some(1))?;
|
||||
if let QueryError::FieldError {
|
||||
err,
|
||||
extended_error,
|
||||
} = err
|
||||
{
|
||||
let mut map = serde_json::Map::new();
|
||||
|
||||
seq.end()
|
||||
map.insert("message".to_string(), err.to_string().into());
|
||||
map.insert(
|
||||
"locations".to_string(),
|
||||
serde_json::json!([{"line": pos.line, "column": pos.column}]),
|
||||
);
|
||||
|
||||
if let Some(path) = path {
|
||||
map.insert("path".to_string(), path.clone());
|
||||
}
|
||||
|
||||
if let Some(obj @ serde_json::Value::Object(_)) = extended_error {
|
||||
map.insert("extensions".to_string(), obj.clone());
|
||||
}
|
||||
seq.serialize_element(&serde_json::Value::Object(map))?;
|
||||
} else {
|
||||
seq.serialize_element(&serde_json::json!({
|
||||
"message": err.to_string(),
|
||||
"locations": [{"line": pos.line, "column": pos.column}]
|
||||
}))?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
Error::Rule { errors } => {
|
||||
let mut seq = serializer.serialize_seq(Some(1))?;
|
||||
for error in errors {
|
||||
seq.serialize_element(&serde_json::json!({
|
||||
"message": error.message,
|
||||
"locations": error.locations.iter().map(|pos| serde_json::json!({"line": pos.line, "column": pos.column})).collect::<Vec<_>>(),
|
||||
}))?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,52 +153,9 @@ impl<'a> Serialize for GQLErrorPos<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
struct GQLPositionError<'a>(&'a PositionError);
|
||||
|
||||
impl<'a> Serialize for GQLPositionError<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut map = serializer.serialize_map(None)?;
|
||||
map.serialize_entry("message", &self.0.inner.to_string())?;
|
||||
map.serialize_entry(
|
||||
"locations",
|
||||
std::slice::from_ref(&GQLErrorPos(&self.0.position)),
|
||||
)?;
|
||||
if let Some(extensions) = get_error_extensions(&self.0.inner) {
|
||||
map.serialize_entry("extensions", &extensions)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
struct GQLRuleError<'a>(&'a RuleError);
|
||||
|
||||
impl<'a> Serialize for GQLRuleError<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut map = serializer.serialize_map(None)?;
|
||||
map.serialize_entry("message", &self.0.message)?;
|
||||
map.serialize_entry(
|
||||
"locations",
|
||||
&self
|
||||
.0
|
||||
.locations
|
||||
.iter()
|
||||
.map(|pos| GQLErrorPos(pos))
|
||||
.collect::<Vec<_>>(),
|
||||
)?;
|
||||
map.end()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ErrorWithPosition;
|
||||
use graphql_parser::Pos;
|
||||
use serde_json::json;
|
||||
|
||||
|
@ -235,7 +207,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_response_data() {
|
||||
let resp = GQLResponse(Ok(QueryResult {
|
||||
let resp = GQLResponse(Ok(QueryResponse {
|
||||
data: json!({"ok": true}),
|
||||
extensions: None,
|
||||
}));
|
||||
|
@ -250,13 +222,20 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_error_with_extension() {
|
||||
let err = ExtendedError(
|
||||
"MyErrorMessage".to_owned(),
|
||||
json!({
|
||||
"code": "MY_TEST_CODE"
|
||||
}),
|
||||
);
|
||||
fn test_field_error_with_extension() {
|
||||
let err = Error::Query {
|
||||
pos: Pos {
|
||||
line: 10,
|
||||
column: 20,
|
||||
},
|
||||
path: None,
|
||||
err: QueryError::FieldError {
|
||||
err: anyhow::anyhow!("MyErrorMessage"),
|
||||
extended_error: Some(json!({
|
||||
"code": "MY_TEST_CODE"
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
let resp = GQLResponse(Err(err.into()));
|
||||
|
||||
|
@ -267,20 +246,8 @@ mod tests {
|
|||
"message":"MyErrorMessage",
|
||||
"extensions": {
|
||||
"code": "MY_TEST_CODE"
|
||||
}
|
||||
}]
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response_error() {
|
||||
let resp = GQLResponse(Err(anyhow::anyhow!("error")));
|
||||
assert_eq!(
|
||||
serde_json::to_value(resp).unwrap(),
|
||||
json!({
|
||||
"errors": [{
|
||||
"message":"error"
|
||||
},
|
||||
"locations": [{"line": 10, "column": 20}]
|
||||
}]
|
||||
})
|
||||
);
|
||||
|
@ -288,17 +255,19 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_response_error_with_pos() {
|
||||
let resp = GQLResponse(Err(anyhow::anyhow!("error")
|
||||
.with_position(Pos {
|
||||
let resp = GQLResponse(Err(Error::Query {
|
||||
pos: Pos {
|
||||
line: 10,
|
||||
column: 20,
|
||||
})
|
||||
.into()));
|
||||
},
|
||||
path: None,
|
||||
err: QueryError::NotSupported,
|
||||
}));
|
||||
assert_eq!(
|
||||
serde_json::to_value(resp).unwrap(),
|
||||
json!({
|
||||
"errors": [{
|
||||
"message":"error",
|
||||
"message":"Not supported.",
|
||||
"locations": [
|
||||
{"line": 10, "column": 20}
|
||||
]
|
||||
|
@ -307,12 +276,3 @@ mod tests {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_error_extensions(err: &crate::Error) -> Option<&serde_json::Value> {
|
||||
if let Some(extended_err) = err.downcast_ref::<ExtendedError>() {
|
||||
if extended_err.1.is_object() {
|
||||
return Some(&extended_err.1);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
|
16
src/lib.rs
16
src/lib.rs
|
@ -100,11 +100,10 @@ pub mod http;
|
|||
|
||||
pub use base::{Scalar, Type};
|
||||
pub use context::{Context, QueryPathSegment, Variables};
|
||||
pub use error::{
|
||||
ErrorWithPosition, ExtendedError, PositionError, QueryError, QueryParseError, ResultExt,
|
||||
};
|
||||
pub use error::{Error, FieldError, FieldResult, QueryError, ResultExt};
|
||||
pub use graphql_parser::query::Value;
|
||||
pub use query::{QueryBuilder, QueryResult};
|
||||
pub use graphql_parser::Pos;
|
||||
pub use query::{QueryBuilder, QueryResponse};
|
||||
pub use registry::CacheControl;
|
||||
pub use scalars::ID;
|
||||
pub use schema::{publish, Schema};
|
||||
|
@ -118,10 +117,7 @@ pub use types::{
|
|||
};
|
||||
|
||||
/// Result type, are actually `anyhow::Result<T>`
|
||||
pub type Result<T> = anyhow::Result<T>;
|
||||
|
||||
/// Error type, are actually `anyhow::Error`
|
||||
pub type Error = anyhow::Error;
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
// internal types
|
||||
#[doc(hidden)]
|
||||
|
@ -176,7 +172,7 @@ pub use types::{EnumItem, EnumType};
|
|||
/// - Option<T>, such as `Option<i32>`
|
||||
/// - Object and &Object
|
||||
/// - Enum
|
||||
/// - Result<T, E>, such as `Result<i32, E>`
|
||||
/// - FieldResult<T, E>, such as `FieldResult<i32, E>`
|
||||
///
|
||||
/// # Context
|
||||
///
|
||||
|
@ -211,7 +207,7 @@ pub use types::{EnumItem, EnumType};
|
|||
/// }
|
||||
///
|
||||
/// #[field(desc = "value with error")]
|
||||
/// async fn value_with_error(&self) -> Result<i32> {
|
||||
/// async fn value_with_error(&self) -> FieldResult<i32> {
|
||||
/// Ok(self.value)
|
||||
/// }
|
||||
///
|
||||
|
|
24
src/query.rs
24
src/query.rs
|
@ -1,18 +1,19 @@
|
|||
use crate::context::Data;
|
||||
use crate::extensions::BoxExtension;
|
||||
use crate::registry::CacheControl;
|
||||
use crate::{ContextBase, OutputValueType, Result, Schema};
|
||||
use crate::{ContextBase, Error, OutputValueType, Result, Schema};
|
||||
use crate::{ObjectType, QueryError, Variables};
|
||||
use bytes::Bytes;
|
||||
use graphql_parser::query::{
|
||||
Definition, Document, OperationDefinition, SelectionSet, VariableDefinition,
|
||||
};
|
||||
use graphql_parser::Pos;
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
|
||||
/// Query result
|
||||
pub struct QueryResult {
|
||||
/// Query response
|
||||
pub struct QueryResponse {
|
||||
/// Data of query result
|
||||
pub data: serde_json::Value,
|
||||
|
||||
|
@ -126,16 +127,19 @@ impl<Query, Mutation, Subscription> QueryBuilder<Query, Mutation, Subscription>
|
|||
}
|
||||
|
||||
/// Execute the query.
|
||||
pub async fn execute(self) -> Result<QueryResult>
|
||||
pub async fn execute(self) -> Result<QueryResponse>
|
||||
where
|
||||
Query: ObjectType + Send + Sync,
|
||||
Mutation: ObjectType + Send + Sync,
|
||||
{
|
||||
let resolve_id = AtomicUsize::default();
|
||||
let mut fragments = HashMap::new();
|
||||
let (selection_set, variable_definitions, is_query) = self
|
||||
.current_operation()
|
||||
.ok_or_else(|| QueryError::MissingOperation)?;
|
||||
let (selection_set, variable_definitions, is_query) =
|
||||
self.current_operation().ok_or_else(|| Error::Query {
|
||||
pos: Pos::default(),
|
||||
path: None,
|
||||
err: QueryError::MissingOperation,
|
||||
})?;
|
||||
|
||||
for definition in &self.document.definitions {
|
||||
if let Definition::Fragment(fragment) = &definition {
|
||||
|
@ -158,13 +162,13 @@ impl<Query, Mutation, Subscription> QueryBuilder<Query, Mutation, Subscription>
|
|||
|
||||
self.extensions.iter().for_each(|e| e.execution_start());
|
||||
let data = if is_query {
|
||||
OutputValueType::resolve(&self.schema.0.query, &ctx).await?
|
||||
OutputValueType::resolve(&self.schema.0.query, &ctx, selection_set.span.0).await?
|
||||
} else {
|
||||
OutputValueType::resolve(&self.schema.0.mutation, &ctx).await?
|
||||
OutputValueType::resolve(&self.schema.0.mutation, &ctx, selection_set.span.0).await?
|
||||
};
|
||||
self.extensions.iter().for_each(|e| e.execution_end());
|
||||
|
||||
let res = QueryResult {
|
||||
let res = QueryResponse {
|
||||
data,
|
||||
extensions: if !self.extensions.is_empty() {
|
||||
Some(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::base::BoxFieldFuture;
|
||||
use crate::extensions::ResolveInfo;
|
||||
use crate::{ContextSelectionSet, Error, ErrorWithPosition, ObjectType, QueryError, Result};
|
||||
use crate::{ContextSelectionSet, Error, ObjectType, QueryError, Result};
|
||||
use futures::{future, TryFutureExt};
|
||||
use graphql_parser::query::{Selection, TypeCondition};
|
||||
use std::iter::FromIterator;
|
||||
|
@ -24,10 +24,13 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
|
|||
futures: &mut Vec<BoxFieldFuture<'a>>,
|
||||
) -> Result<()> {
|
||||
if ctx.items.is_empty() {
|
||||
anyhow::bail!(QueryError::MustHaveSubFields {
|
||||
object: T::type_name().to_string(),
|
||||
}
|
||||
.with_position(ctx.span.0));
|
||||
return Err(Error::Query {
|
||||
pos: ctx.span.0,
|
||||
path: None,
|
||||
err: QueryError::MustHaveSubFields {
|
||||
object: T::type_name().to_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
for selection in &ctx.item.items {
|
||||
|
@ -69,11 +72,14 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
|
|||
{
|
||||
Some(ty) => &ty,
|
||||
None => {
|
||||
anyhow::bail!(QueryError::FieldNotFound {
|
||||
field_name: field.name.clone(),
|
||||
object: T::type_name().to_string(),
|
||||
}
|
||||
.with_position(field.position));
|
||||
return Err(Error::Query {
|
||||
pos: field.position,
|
||||
path: None,
|
||||
err: QueryError::FieldNotFound {
|
||||
field_name: field.name.clone(),
|
||||
object: T::type_name().to_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -112,10 +118,13 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
|
|||
futures,
|
||||
)?;
|
||||
} else {
|
||||
return Err(QueryError::UnknownFragment {
|
||||
name: fragment_spread.fragment_name.clone(),
|
||||
}
|
||||
.into());
|
||||
return Err(Error::Query {
|
||||
pos: fragment_spread.position,
|
||||
path: None,
|
||||
err: QueryError::UnknownFragment {
|
||||
name: fragment_spread.fragment_name.clone(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Selection::InlineFragment(inline_fragment) => {
|
||||
|
@ -126,6 +135,7 @@ pub fn collect_fields<'a, T: ObjectType + Send + Sync>(
|
|||
if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition {
|
||||
root.collect_inline_fields(
|
||||
name,
|
||||
inline_fragment.position,
|
||||
&ctx.with_selection_set(&inline_fragment.selection_set),
|
||||
futures,
|
||||
)?;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
impl_scalar_internal, registry, ContextSelectionSet, OutputValueType, Result, Scalar, Type,
|
||||
Value,
|
||||
impl_scalar_internal, registry, ContextSelectionSet, OutputValueType, Pos, Result, Scalar,
|
||||
Type, Value,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
|
@ -55,7 +55,11 @@ impl<'a> Type for &'a str {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> OutputValueType for &'a str {
|
||||
async fn resolve(value: &Self, _: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
async fn resolve(
|
||||
value: &Self,
|
||||
_: &ContextSelectionSet<'_>,
|
||||
_pos: Pos,
|
||||
) -> Result<serde_json::Value> {
|
||||
Ok((*value).into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::subscription::{SubscriptionConnectionBuilder, SubscriptionStub, Subsc
|
|||
use crate::types::QueryRoot;
|
||||
use crate::validation::{check_rules, CheckResult};
|
||||
use crate::{
|
||||
ContextSelectionSet, ObjectType, QueryError, QueryParseError, Result, SubscriptionType, Type,
|
||||
ContextSelectionSet, Error, ObjectType, Pos, QueryError, Result, SubscriptionType, Type,
|
||||
Variables,
|
||||
};
|
||||
use futures::channel::mpsc;
|
||||
|
@ -219,7 +219,7 @@ where
|
|||
.map(|factory| factory())
|
||||
.collect::<Vec<_>>();
|
||||
extensions.iter().for_each(|e| e.parse_start(source));
|
||||
let document = parse_query(source).map_err(|err| QueryParseError(err.to_string()))?;
|
||||
let document = parse_query(source).map_err(Into::<Error>::into)?;
|
||||
extensions.iter().for_each(|e| e.parse_end());
|
||||
|
||||
extensions.iter().for_each(|e| e.validation_start());
|
||||
|
@ -232,13 +232,13 @@ where
|
|||
|
||||
if let Some(limit_complexity) = self.0.complexity {
|
||||
if complexity > limit_complexity {
|
||||
return Err(QueryError::TooComplex.into());
|
||||
return Err(QueryError::TooComplex.into_error(Pos { line: 0, column: 0 }));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(limit_depth) = self.0.depth {
|
||||
if depth > limit_depth {
|
||||
return Err(QueryError::TooDeep.into());
|
||||
return Err(QueryError::TooDeep.into_error(Pos { line: 0, column: 0 }));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -263,7 +263,7 @@ where
|
|||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let document = parse_query(source).map_err(|err| QueryParseError(err.to_string()))?;
|
||||
let document = parse_query(source).map_err(Into::<Error>::into)?;
|
||||
check_rules(&self.0.registry, &document)?;
|
||||
|
||||
let mut fragments = HashMap::new();
|
||||
|
@ -288,8 +288,9 @@ where
|
|||
QueryError::UnknownOperationNamed {
|
||||
name: name.to_string(),
|
||||
}
|
||||
.into_error(Pos::default())
|
||||
} else {
|
||||
QueryError::MissingOperation
|
||||
QueryError::MissingOperation.into_error(Pos::default())
|
||||
})?;
|
||||
|
||||
let mut types = HashMap::new();
|
||||
|
@ -358,7 +359,7 @@ fn create_subscription_types<T: SubscriptionType>(
|
|||
return Err(QueryError::UnknownFragment {
|
||||
name: fragment_spread.fragment_name.clone(),
|
||||
}
|
||||
.into());
|
||||
.into_error(fragment_spread.position));
|
||||
}
|
||||
}
|
||||
Selection::InlineFragment(inline_fragment) => {
|
||||
|
|
|
@ -34,6 +34,9 @@ impl<Query, Mutation, Subscription> SubscriptionStubs<Query, Mutation, Subscript
|
|||
///
|
||||
/// You can customize your transport by implementing this trait.
|
||||
pub trait SubscriptionTransport: Send + Sync + Unpin + 'static {
|
||||
/// The error type.
|
||||
type Error;
|
||||
|
||||
/// Parse the request data here.
|
||||
/// If you have a new request, create a `SubscriptionStub` with the `Schema::create_subscription_stub`, and then call `SubscriptionStubs::add`.
|
||||
/// You can return a `Byte`, which will be sent to the client. If it returns an error, the connection will be broken.
|
||||
|
@ -42,7 +45,7 @@ pub trait SubscriptionTransport: Send + Sync + Unpin + 'static {
|
|||
schema: &Schema<Query, Mutation, Subscription>,
|
||||
stubs: &mut SubscriptionStubs<Query, Mutation, Subscription>,
|
||||
data: Bytes,
|
||||
) -> Result<Option<Bytes>>
|
||||
) -> std::result::Result<Option<Bytes>, Self::Error>
|
||||
where
|
||||
Query: ObjectType + Sync + Send + 'static,
|
||||
Mutation: ObjectType + Sync + Send + 'static,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::http::{GQLError, GQLRequest, GQLResponse};
|
||||
use crate::{
|
||||
ObjectType, QueryResult, Result, Schema, SubscriptionStubs, SubscriptionTransport,
|
||||
ObjectType, QueryResponse, Result, Schema, SubscriptionStubs, SubscriptionTransport,
|
||||
SubscriptionType, Variables,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
|
@ -22,12 +22,14 @@ pub struct WebSocketTransport {
|
|||
}
|
||||
|
||||
impl SubscriptionTransport for WebSocketTransport {
|
||||
type Error = String;
|
||||
|
||||
fn handle_request<Query, Mutation, Subscription>(
|
||||
&mut self,
|
||||
schema: &Schema<Query, Mutation, Subscription>,
|
||||
stubs: &mut SubscriptionStubs<Query, Mutation, Subscription>,
|
||||
data: Bytes,
|
||||
) -> Result<Option<Bytes>>
|
||||
) -> std::result::Result<Option<Bytes>, Self::Error>
|
||||
where
|
||||
Query: ObjectType + Sync + Send + 'static,
|
||||
Mutation: ObjectType + Sync + Send + 'static,
|
||||
|
@ -92,10 +94,10 @@ impl SubscriptionTransport for WebSocketTransport {
|
|||
}
|
||||
Ok(None)
|
||||
}
|
||||
"connection_terminate" => Err(anyhow::anyhow!("connection_terminate")),
|
||||
_ => Err(anyhow::anyhow!("unknown op")),
|
||||
"connection_terminate" => Err("connection_terminate".to_string()),
|
||||
_ => Err("unknown op".to_string()),
|
||||
},
|
||||
Err(err) => Err(err.into()),
|
||||
Err(err) => Err(err.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +108,7 @@ impl SubscriptionTransport for WebSocketTransport {
|
|||
ty: "data".to_string(),
|
||||
id: Some(id.clone()),
|
||||
payload: Some(
|
||||
serde_json::to_value(GQLResponse(result.map(|data| QueryResult {
|
||||
serde_json::to_value(GQLResponse(result.map(|data| QueryResponse {
|
||||
data,
|
||||
extensions: None,
|
||||
})))
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::types::connection::edge::Edge;
|
||||
use crate::types::connection::page_info::PageInfo;
|
||||
use crate::{
|
||||
do_resolve, registry, Context, ContextSelectionSet, ErrorWithPosition, ObjectType,
|
||||
OutputValueType, QueryError, Result, Type,
|
||||
do_resolve, registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, Pos,
|
||||
QueryError, Result, Type,
|
||||
};
|
||||
use graphql_parser::query::Field;
|
||||
use inflector::Inflector;
|
||||
|
@ -131,9 +131,7 @@ impl<T: OutputValueType + Send + Sync, E: ObjectType + Sync + Send> ObjectType
|
|||
if field.name.as_str() == "pageInfo" {
|
||||
let ctx_obj = ctx.with_selection_set(&field.selection_set);
|
||||
let page_info = &self.page_info;
|
||||
return OutputValueType::resolve(page_info, &ctx_obj)
|
||||
.await
|
||||
.map_err(|err| err.with_position(field.position).into());
|
||||
return OutputValueType::resolve(page_info, &ctx_obj, field.position).await;
|
||||
} else if field.name.as_str() == "edges" {
|
||||
let ctx_obj = ctx.with_selection_set(&field.selection_set);
|
||||
let edges = self
|
||||
|
@ -145,9 +143,7 @@ impl<T: OutputValueType + Send + Sync, E: ObjectType + Sync + Send> ObjectType
|
|||
node,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
return OutputValueType::resolve(&edges, &ctx_obj)
|
||||
.await
|
||||
.map_err(|err| err.with_position(field.position).into());
|
||||
return OutputValueType::resolve(&edges, &ctx_obj, field.position).await;
|
||||
} else if field.name.as_str() == "totalCount" {
|
||||
return Ok(self
|
||||
.total_count
|
||||
|
@ -160,16 +156,17 @@ impl<T: OutputValueType + Send + Sync, E: ObjectType + Sync + Send> ObjectType
|
|||
.iter()
|
||||
.map(|(_, _, item)| item)
|
||||
.collect::<Vec<_>>();
|
||||
return OutputValueType::resolve(&items, &ctx_obj)
|
||||
.await
|
||||
.map_err(|err| err.with_position(field.position).into());
|
||||
return OutputValueType::resolve(&items, &ctx_obj, field.position).await;
|
||||
}
|
||||
|
||||
anyhow::bail!(QueryError::FieldNotFound {
|
||||
field_name: field.name.clone(),
|
||||
object: Connection::<T, E>::type_name().to_string(),
|
||||
}
|
||||
.with_position(field.position))
|
||||
Err(Error::Query {
|
||||
pos: field.position,
|
||||
path: None,
|
||||
err: QueryError::FieldNotFound {
|
||||
field_name: field.name.clone(),
|
||||
object: Connection::<T, E>::type_name().to_string(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,7 +174,11 @@ impl<T: OutputValueType + Send + Sync, E: ObjectType + Sync + Send> ObjectType
|
|||
impl<T: OutputValueType + Send + Sync, E: ObjectType + Sync + Send> OutputValueType
|
||||
for Connection<T, E>
|
||||
{
|
||||
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
async fn resolve(
|
||||
value: &Self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
_pos: Pos,
|
||||
) -> Result<serde_json::Value> {
|
||||
do_resolve(ctx, value).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::{
|
||||
do_resolve, registry, Context, ContextSelectionSet, ErrorWithPosition, ObjectType,
|
||||
OutputValueType, Result, Type,
|
||||
do_resolve, registry, Context, ContextSelectionSet, ObjectType, OutputValueType, Result, Type,
|
||||
};
|
||||
use graphql_parser::query::Field;
|
||||
use graphql_parser::Pos;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
@ -81,9 +81,7 @@ where
|
|||
async fn resolve_field(&self, ctx: &Context<'_>, field: &Field) -> Result<serde_json::Value> {
|
||||
if field.name.as_str() == "node" {
|
||||
let ctx_obj = ctx.with_selection_set(&field.selection_set);
|
||||
return OutputValueType::resolve(self.node, &ctx_obj)
|
||||
.await
|
||||
.map_err(|err| err.with_position(field.position).into());
|
||||
return OutputValueType::resolve(self.node, &ctx_obj, field.position).await;
|
||||
} else if field.name.as_str() == "cursor" {
|
||||
return Ok(self.cursor.into());
|
||||
}
|
||||
|
@ -98,7 +96,11 @@ where
|
|||
T: OutputValueType + Send + Sync + 'a,
|
||||
E: ObjectType + Sync + Send + 'a,
|
||||
{
|
||||
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
async fn resolve(
|
||||
value: &Self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
_pos: Pos,
|
||||
) -> Result<serde_json::Value> {
|
||||
do_resolve(ctx, value).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ mod edge;
|
|||
mod page_info;
|
||||
mod slice;
|
||||
|
||||
use crate::{Context, ObjectType, QueryError, Result};
|
||||
use crate::{Context, FieldResult, ObjectType};
|
||||
|
||||
pub use connection_type::Connection;
|
||||
|
||||
|
@ -65,7 +65,7 @@ impl EmptyEdgeFields {}
|
|||
/// type Element = i32;
|
||||
/// type EdgeFieldsObj = DiffFields;
|
||||
///
|
||||
/// async fn query_operation(&self, operation: &QueryOperation<'_>) -> Result<Connection<Self::Element, Self::EdgeFieldsObj>> {
|
||||
/// async fn query_operation(&self, operation: &QueryOperation<'_>) -> FieldResult<Connection<Self::Element, Self::EdgeFieldsObj>> {
|
||||
/// let (start, end) = match operation {
|
||||
/// QueryOperation::Forward {after, limit} => {
|
||||
/// let start = after.and_then(|after| base64::decode(after).ok())
|
||||
|
@ -97,7 +97,7 @@ impl EmptyEdgeFields {}
|
|||
/// before: Option<String>,
|
||||
/// first: Option<i32>,
|
||||
/// last: Option<i32>
|
||||
/// ) -> Result<Connection<i32, DiffFields>> {
|
||||
/// ) -> FieldResult<Connection<i32, DiffFields>> {
|
||||
/// Numbers.query(ctx, after, before, first, last).await
|
||||
/// }
|
||||
/// }
|
||||
|
@ -138,26 +138,17 @@ pub trait DataSource: Sync + Send {
|
|||
/// Execute the query.
|
||||
async fn query(
|
||||
&self,
|
||||
ctx: &Context<'_>,
|
||||
_ctx: &Context<'_>,
|
||||
after: Option<String>,
|
||||
before: Option<String>,
|
||||
first: Option<i32>,
|
||||
last: Option<i32>,
|
||||
) -> Result<Connection<Self::Element, Self::EdgeFieldsObj>> {
|
||||
) -> FieldResult<Connection<Self::Element, Self::EdgeFieldsObj>> {
|
||||
let operation = if let Some(after) = &after {
|
||||
QueryOperation::Forward {
|
||||
after: Some(after),
|
||||
limit: match first {
|
||||
Some(value) => {
|
||||
if value < 0 {
|
||||
return Err(QueryError::ArgumentMustBeNonNegative {
|
||||
field_name: ctx.name.clone(),
|
||||
}
|
||||
.into());
|
||||
} else {
|
||||
value as usize
|
||||
}
|
||||
}
|
||||
Some(value) => value.max(0) as usize,
|
||||
None => 10,
|
||||
},
|
||||
}
|
||||
|
@ -165,42 +156,19 @@ pub trait DataSource: Sync + Send {
|
|||
QueryOperation::Backward {
|
||||
before: Some(before),
|
||||
limit: match last {
|
||||
Some(value) => {
|
||||
if value < 0 {
|
||||
return Err(QueryError::ArgumentMustBeNonNegative {
|
||||
field_name: ctx.name.clone(),
|
||||
}
|
||||
.into());
|
||||
} else {
|
||||
value as usize
|
||||
}
|
||||
}
|
||||
Some(value) => value.max(0) as usize,
|
||||
None => 10,
|
||||
},
|
||||
}
|
||||
} else if let Some(first) = first {
|
||||
QueryOperation::Forward {
|
||||
after: None,
|
||||
limit: if first < 0 {
|
||||
return Err(QueryError::ArgumentMustBeNonNegative {
|
||||
field_name: ctx.name.clone(),
|
||||
}
|
||||
.into());
|
||||
} else {
|
||||
first as usize
|
||||
},
|
||||
limit: first.max(0) as usize,
|
||||
}
|
||||
} else if let Some(last) = last {
|
||||
QueryOperation::Backward {
|
||||
before: None,
|
||||
limit: if last < 0 {
|
||||
return Err(QueryError::ArgumentMustBeNonNegative {
|
||||
field_name: ctx.name.clone(),
|
||||
}
|
||||
.into());
|
||||
} else {
|
||||
last as usize
|
||||
},
|
||||
limit: last.max(0) as usize,
|
||||
}
|
||||
} else {
|
||||
QueryOperation::Forward {
|
||||
|
@ -216,5 +184,5 @@ pub trait DataSource: Sync + Send {
|
|||
async fn query_operation(
|
||||
&self,
|
||||
operation: &QueryOperation<'_>,
|
||||
) -> Result<Connection<Self::Element, Self::EdgeFieldsObj>>;
|
||||
) -> FieldResult<Connection<Self::Element, Self::EdgeFieldsObj>>;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::types::connection::{EmptyEdgeFields, QueryOperation};
|
||||
use crate::{Connection, DataSource, Result};
|
||||
use crate::{Connection, DataSource, FieldResult};
|
||||
use byteorder::{ReadBytesExt, BE};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
@ -10,7 +10,7 @@ impl<'a, T: Sync> DataSource for &'a [T] {
|
|||
async fn query_operation(
|
||||
&self,
|
||||
operation: &QueryOperation<'_>,
|
||||
) -> Result<Connection<Self::Element, Self::EdgeFieldsObj>> {
|
||||
) -> FieldResult<Connection<Self::Element, Self::EdgeFieldsObj>> {
|
||||
let (start, end) = match operation {
|
||||
QueryOperation::Forward { after, limit } => {
|
||||
let start = after
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::{
|
||||
registry, Context, ContextSelectionSet, ObjectType, OutputValueType, QueryError, Result, Type,
|
||||
registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType, QueryError, Result,
|
||||
Type,
|
||||
};
|
||||
use graphql_parser::query::Field;
|
||||
use graphql_parser::Pos;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Empty mutation
|
||||
|
@ -52,7 +54,15 @@ impl ObjectType for EmptyMutation {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl OutputValueType for EmptyMutation {
|
||||
async fn resolve(_value: &Self, _ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
Err(QueryError::NotConfiguredMutations.into())
|
||||
async fn resolve(
|
||||
_value: &Self,
|
||||
_ctx: &ContextSelectionSet<'_>,
|
||||
pos: Pos,
|
||||
) -> Result<serde_json::Value> {
|
||||
Err(Error::Query {
|
||||
pos,
|
||||
path: None,
|
||||
err: QueryError::NotConfiguredMutations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use crate::{
|
||||
registry, ContextBase, ContextSelectionSet, OutputValueType, QueryError, Result,
|
||||
registry, ContextBase, ContextSelectionSet, Error, OutputValueType, QueryError, Result,
|
||||
SubscriptionType, Type,
|
||||
};
|
||||
use graphql_parser::query::Field;
|
||||
use graphql_parser::Pos;
|
||||
use serde_json::Value;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::borrow::Cow;
|
||||
|
@ -51,7 +52,15 @@ impl SubscriptionType for EmptySubscription {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl OutputValueType for EmptySubscription {
|
||||
async fn resolve(_value: &Self, _ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
Err(QueryError::NotConfiguredSubscriptions.into())
|
||||
async fn resolve(
|
||||
_value: &Self,
|
||||
_ctx: &ContextSelectionSet<'_>,
|
||||
pos: Pos,
|
||||
) -> Result<serde_json::Value> {
|
||||
Err(Error::Query {
|
||||
pos,
|
||||
path: None,
|
||||
err: QueryError::NotConfiguredSubscriptions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{registry, ContextSelectionSet, InputValueType, OutputValueType, Result, Type, Value};
|
||||
use graphql_parser::Pos;
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl<T: Type> Type for Vec<T> {
|
||||
|
@ -34,11 +35,15 @@ impl<T: InputValueType> InputValueType for Vec<T> {
|
|||
#[allow(clippy::ptr_arg)]
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputValueType + Send + Sync> OutputValueType for Vec<T> {
|
||||
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
async fn resolve(
|
||||
value: &Self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
pos: Pos,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut futures = Vec::with_capacity(value.len());
|
||||
for (idx, item) in value.iter().enumerate() {
|
||||
let ctx_idx = ctx.with_index(idx);
|
||||
futures.push(async move { OutputValueType::resolve(item, &ctx_idx).await });
|
||||
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, pos).await });
|
||||
}
|
||||
Ok(futures::future::try_join_all(futures).await?.into())
|
||||
}
|
||||
|
@ -56,11 +61,15 @@ impl<T: Type> Type for &[T] {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputValueType + Send + Sync> OutputValueType for &[T] {
|
||||
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
async fn resolve(
|
||||
value: &Self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
pos: Pos,
|
||||
) -> Result<serde_json::Value> {
|
||||
let mut futures = Vec::with_capacity(value.len());
|
||||
for (idx, item) in (*value).iter().enumerate() {
|
||||
let ctx_idx = ctx.with_index(idx);
|
||||
futures.push(async move { OutputValueType::resolve(item, &ctx_idx).await });
|
||||
futures.push(async move { OutputValueType::resolve(item, &ctx_idx, pos).await });
|
||||
}
|
||||
Ok(futures::future::try_join_all(futures).await?.into())
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use crate::{registry, ContextSelectionSet, InputValueType, OutputValueType, Result, Type, Value};
|
||||
use crate::{
|
||||
registry, ContextSelectionSet, InputValueType, OutputValueType, Pos, Result, Type, Value,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
impl<T: Type> Type for Option<T> {
|
||||
|
@ -27,10 +29,13 @@ impl<T: InputValueType> InputValueType for Option<T> {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: OutputValueType + Sync> OutputValueType for Option<T> {
|
||||
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> where
|
||||
{
|
||||
async fn resolve(
|
||||
value: &Self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
pos: Pos,
|
||||
) -> Result<serde_json::Value> where {
|
||||
if let Some(inner) = value {
|
||||
OutputValueType::resolve(inner, ctx).await
|
||||
OutputValueType::resolve(inner, ctx, pos).await
|
||||
} else {
|
||||
Ok(serde_json::Value::Null)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::model::{__Schema, __Type};
|
||||
use crate::{
|
||||
do_resolve, registry, Context, ContextSelectionSet, ErrorWithPosition, ObjectType,
|
||||
OutputValueType, QueryError, Result, Type, Value,
|
||||
do_resolve, registry, Context, ContextSelectionSet, Error, ObjectType, OutputValueType,
|
||||
QueryError, Result, Type, Value,
|
||||
};
|
||||
use graphql_parser::query::Field;
|
||||
use graphql_parser::Pos;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
@ -69,11 +70,14 @@ impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
|
|||
async fn resolve_field(&self, ctx: &Context<'_>, field: &Field) -> Result<serde_json::Value> {
|
||||
if field.name.as_str() == "__schema" {
|
||||
if self.disable_introspection {
|
||||
return Err(QueryError::FieldNotFound {
|
||||
field_name: field.name.clone(),
|
||||
object: Self::type_name().to_string(),
|
||||
}
|
||||
.into());
|
||||
return Err(Error::Query {
|
||||
pos: field.position,
|
||||
path: Some(ctx.path_node.as_ref().unwrap().to_json()),
|
||||
err: QueryError::FieldNotFound {
|
||||
field_name: field.name.clone(),
|
||||
object: Self::type_name().to_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let ctx_obj = ctx.with_selection_set(&field.selection_set);
|
||||
|
@ -82,11 +86,11 @@ impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
|
|||
registry: &ctx.registry,
|
||||
},
|
||||
&ctx_obj,
|
||||
field.position,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| err.with_position(field.position).into());
|
||||
.await;
|
||||
} else if field.name.as_str() == "__type" {
|
||||
let type_name: String = ctx.param_value("name", || Value::Null)?;
|
||||
let type_name: String = ctx.param_value("name", field.position, || Value::Null)?;
|
||||
let ctx_obj = ctx.with_selection_set(&field.selection_set);
|
||||
return OutputValueType::resolve(
|
||||
&ctx.registry
|
||||
|
@ -94,9 +98,9 @@ impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
|
|||
.get(&type_name)
|
||||
.map(|ty| __Type::new_simple(ctx.registry, ty)),
|
||||
&ctx_obj,
|
||||
field.position,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| err.with_position(field.position).into());
|
||||
.await;
|
||||
}
|
||||
|
||||
self.inner.resolve_field(ctx, field).await
|
||||
|
@ -105,7 +109,11 @@ impl<T: ObjectType + Send + Sync> ObjectType for QueryRoot<T> {
|
|||
|
||||
#[async_trait::async_trait]
|
||||
impl<T: ObjectType + Send + Sync> OutputValueType for QueryRoot<T> {
|
||||
async fn resolve(value: &Self, ctx: &ContextSelectionSet<'_>) -> Result<serde_json::Value> {
|
||||
async fn resolve(
|
||||
value: &Self,
|
||||
ctx: &ContextSelectionSet<'_>,
|
||||
_pos: Pos,
|
||||
) -> Result<serde_json::Value> {
|
||||
do_resolve(ctx, value).await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,8 @@ mod utils;
|
|||
mod visitor;
|
||||
mod visitors;
|
||||
|
||||
use crate::error::RuleErrors;
|
||||
use crate::registry::Registry;
|
||||
use crate::{CacheControl, Result};
|
||||
use crate::{CacheControl, Error, Result};
|
||||
use graphql_parser::query::Document;
|
||||
use visitor::{visit, VisitorContext, VisitorNil};
|
||||
|
||||
|
@ -57,7 +56,7 @@ pub fn check_rules(registry: &Registry, doc: &Document) -> Result<CheckResult> {
|
|||
|
||||
visit(&mut visitor, &mut ctx, doc);
|
||||
if !ctx.errors.is_empty() {
|
||||
return Err(RuleErrors { errors: ctx.errors }.into());
|
||||
return Err(Error::Rule { errors: ctx.errors });
|
||||
}
|
||||
Ok(CheckResult {
|
||||
cache_control,
|
||||
|
|
Loading…
Reference in New Issue
Block a user