Skip to content

Commit

Permalink
The error reason can be returned when the input value is parsed incor…
Browse files Browse the repository at this point in the history
…rectly. #70
  • Loading branch information
sunli829 committed May 10, 2020
1 parent 2267e96 commit 6d7d648
Show file tree
Hide file tree
Showing 28 changed files with 259 additions and 141 deletions.
2 changes: 1 addition & 1 deletion async-graphql-derive/src/enum.rs
Expand Up @@ -156,7 +156,7 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
}

impl #crate_name::InputValueType for #ident {
fn parse(value: &#crate_name::Value) -> Option<Self> {
fn parse(value: &#crate_name::Value) -> #crate_name::InputValueResult<Self> {
#crate_name::EnumType::parse_enum(value)
}
}
Expand Down
6 changes: 3 additions & 3 deletions async-graphql-derive/src/input_object.rs
Expand Up @@ -129,14 +129,14 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
}

impl #crate_name::InputValueType for #ident {
fn parse(value: &#crate_name::Value) -> Option<Self> {
fn parse(value: &#crate_name::Value) -> #crate_name::InputValueResult<Self> {
use #crate_name::Type;

if let #crate_name::Value::Object(obj) = value {
#(#get_fields)*
Some(Self { #(#fields),* })
Ok(Self { #(#fields),* })
} else {
None
Err(#crate_name::InputValueError::ExpectedType)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion async-graphql-derive/src/lib.rs
Expand Up @@ -163,7 +163,7 @@ pub fn Scalar(args: TokenStream, input: TokenStream) -> TokenStream {
}

impl #generic #crate_name::InputValueType for #self_ty #where_clause {
fn parse(value: &#crate_name::Value) -> Option<Self> {
fn parse(value: &#crate_name::Value) -> #crate_name::InputValueResult<Self> {
<#self_ty as #crate_name::ScalarType>::parse(value)
}
}
Expand Down
2 changes: 1 addition & 1 deletion async-graphql-derive/src/object.rs
Expand Up @@ -126,7 +126,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
});
key_getter.push(quote! {
params.get(#name).and_then(|value| {
let value: Option<#ty> = #crate_name::InputValueType::parse(value);
let value: Option<#ty> = #crate_name::InputValueType::parse(value).ok();
value
})
});
Expand Down
8 changes: 4 additions & 4 deletions docs/en/src/custom_scalars.md
Expand Up @@ -19,13 +19,13 @@ impl ScalarType for StringNumber {
"StringNumber"
}

fn parse(value: &Value) -> Option<Self> {
fn parse(value: &Value) -> InputValueResult<Self> {
if let Value::String(value) = value {
// Parse the integer value
value.parse().ok().map(StringNumber)
value.parse().map(StringNumber)?
} else {
// If the type does not match, return None
None
// If the type does not match
InputValueError::ExpectedType
}
}

Expand Down
8 changes: 4 additions & 4 deletions docs/zh-CN/src/custom_scalars.md
Expand Up @@ -19,13 +19,13 @@ impl ScalarType for StringNumber {
"StringNumber"
}

fn parse(value: &Value) -> Option<Self> {
fn parse(value: &Value) -> InputValueResult<Self> {
if let Value::String(value) = value {
// 解析整数
value.parse().ok().map(StringNumber)
value.parse().map(StringNumber)?
} else {
// 类型不匹配则直接返回None
None
// 类型不匹配
InputValueError::ExpectedType
}
}

Expand Down
18 changes: 11 additions & 7 deletions src/base.rs
@@ -1,7 +1,8 @@
use crate::parser::Pos;
use crate::registry::Registry;
use crate::{
registry, Context, ContextSelectionSet, FieldResult, Positioned, QueryError, Result, Value, ID,
registry, Context, ContextSelectionSet, FieldResult, InputValueResult, Positioned, QueryError,
Result, Value, ID,
};
use std::borrow::Cow;
use std::future::Future;
Expand Down Expand Up @@ -51,7 +52,7 @@ pub trait Type {
/// Represents a GraphQL input value
pub trait InputValueType: Type + Sized {
/// Parse from `Value`
fn parse(value: &Value) -> Option<Self>;
fn parse(value: &Value) -> InputValueResult<Self>;
}

/// Represents a GraphQL output value
Expand Down Expand Up @@ -127,11 +128,11 @@ pub trait InputObjectType: InputValueType {}
/// "MyInt"
/// }
///
/// fn parse(value: &Value) -> Option<Self> {
/// fn parse(value: &Value) -> InputValueResult<Self> {
/// if let Value::Int(n) = value {
/// Some(MyInt(*n as i32))
/// Ok(MyInt(*n as i32))
/// } else {
/// None
/// Err(InputValueError::ExpectedType)
/// }
/// }
///
Expand All @@ -150,13 +151,16 @@ pub trait ScalarType: Sized + Send {
}

/// Parse a scalar value, return `Some(Self)` if successful, otherwise return `None`.
fn parse(value: &Value) -> Option<Self>;
fn parse(value: &Value) -> InputValueResult<Self>;

/// Checks for a valid scalar value.
///
/// The default implementation is to try to parse it, and in some cases you can implement this on your own to improve performance.
fn is_valid(value: &Value) -> bool {
Self::parse(value).is_some()
match Self::parse(value) {
Ok(_) => true,
_ => false,
}
}

/// Convert the scalar value to json value.
Expand Down
65 changes: 32 additions & 33 deletions src/context.rs
Expand Up @@ -410,16 +410,18 @@ impl<'a, T> ContextBase<'a, T> {
for directive in directives {
if directive.name.as_str() == "skip" {
if let Some(value) = directive.get_argument("if") {
let value = self.resolve_input_value(value.clone_inner(), value.position())?;
let res: bool = InputValueType::parse(&value).ok_or_else(|| {
QueryError::ExpectedType {
expect: bool::qualified_type_name(),
actual: value,
match InputValueType::parse(
&self.resolve_input_value(value.clone_inner(), value.position())?,
) {
Ok(true) => return Ok(true),
Ok(false) => {}
Err(err) => {
return Err(err.into_error(
value.pos,
bool::qualified_type_name(),
value.clone_inner(),
))
}
.into_error(directive.position())
})?;
if res {
return Ok(true);
}
} else {
return Err(QueryError::RequiredDirectiveArgs {
Expand All @@ -431,16 +433,18 @@ impl<'a, T> ContextBase<'a, T> {
}
} else if directive.name.as_str() == "include" {
if let Some(value) = directive.get_argument("if") {
let value = self.resolve_input_value(value.clone_inner(), value.position())?;
let res: bool = InputValueType::parse(&value).ok_or_else(|| {
QueryError::ExpectedType {
expect: bool::qualified_type_name(),
actual: value,
match InputValueType::parse(
&self.resolve_input_value(value.clone_inner(), value.position())?,
) {
Ok(false) => return Ok(true),
Ok(true) => {}
Err(err) => {
return Err(err.into_error(
value.pos,
bool::qualified_type_name(),
value.clone_inner(),
))
}
.into_error(directive.position())
})?;
if !res {
return Ok(true);
}
} else {
return Err(QueryError::RequiredDirectiveArgs {
Expand Down Expand Up @@ -495,25 +499,20 @@ impl<'a> ContextBase<'a, &'a Positioned<Field>> {
Some(value) => {
let pos = value.position();
let value = self.resolve_input_value(value.into_inner(), pos)?;
let res = InputValueType::parse(&value).ok_or_else(|| {
QueryError::ExpectedType {
expect: T::qualified_type_name(),
actual: value,
}
.into_error(pos)
})?;
Ok(res)
match InputValueType::parse(&value) {
Ok(res) => Ok(res),
Err(err) => Err(err.into_error(pos, T::qualified_type_name(), value)),
}
}
None => {
let value = default();
let res = InputValueType::parse(&value).ok_or_else(|| {
QueryError::ExpectedType {
expect: T::qualified_type_name(),
actual: value.clone(),
match InputValueType::parse(&value) {
Ok(res) => Ok(res),
Err(err) => {
// The default value has no valid location.
Err(err.into_error(Pos::default(), T::qualified_type_name(), value))
}
.into_error(self.position())
})?;
Ok(res)
}
}
}
}
Expand Down
49 changes: 46 additions & 3 deletions src/error.rs
@@ -1,5 +1,45 @@
use crate::{Pos, Value};
use std::fmt::Debug;
use std::fmt::{Debug, Display};

/// Input Value Error
#[derive(Debug)]
pub enum InputValueError {
/// Custom input value parsing error.
Custom(String),

/// The type of input value does not match the expectation.
ExpectedType,
}

impl<T: Display> From<T> for InputValueError {
fn from(err: T) -> Self {
InputValueError::Custom(err.to_string())
}
}

impl InputValueError {
#[allow(missing_docs)]
pub fn into_error(self, pos: Pos, expected_type: String, value: Value) -> Error {
match self {
InputValueError::Custom(reason) => Error::Query {
pos,
path: None,
err: QueryError::ParseInputValue { reason },
},
InputValueError::ExpectedType => Error::Query {
pos,
path: None,
err: QueryError::ExpectedInputType {
expect: expected_type,
actual: value,
},
},
}
}
}

/// InputValueResult type
pub type InputValueResult<T> = std::result::Result<T, InputValueError>;

/// FieldError type
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -128,15 +168,18 @@ pub enum QueryError {
#[error("Not supported.")]
NotSupported,

#[error("Expected type \"{expect}\", found {actual}.")]
ExpectedType {
#[error("Expected input type \"{expect}\", found {actual}.")]
ExpectedInputType {
/// Expect input type
expect: String,

/// Actual input type
actual: Value,
},

#[error("Failed to parse input value: {reason}")]
ParseInputValue { reason: String },

#[error("Cannot query field \"{field_name}\" on type \"{object}\".")]
FieldNotFound {
/// Field name
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Expand Up @@ -116,7 +116,8 @@ pub use context::{
Context, ContextBase, Data, Environment, QueryPathNode, QueryPathSegment, Variables,
};
pub use error::{
Error, ErrorExtensions, FieldError, FieldResult, ParseRequestError, QueryError, ResultExt,
Error, ErrorExtensions, FieldError, FieldResult, InputValueError, InputValueResult,
ParseRequestError, QueryError, ResultExt,
};
pub use parser::{Pos, Positioned, Value};
pub use query::{IntoQueryBuilder, IntoQueryBuilderOpts, QueryBuilder, QueryResponse};
Expand Down
6 changes: 3 additions & 3 deletions src/scalars/any.rs
@@ -1,4 +1,4 @@
use crate::{Result, ScalarType, Value};
use crate::{InputValueResult, Result, ScalarType, Value};
use async_graphql_derive::Scalar;
use serde::de::DeserializeOwned;

Expand All @@ -18,8 +18,8 @@ impl ScalarType for Any {
Some("The `_Any` scalar is used to pass representations of entities from external services into the root `_entities` field for execution.")
}

fn parse(value: &Value) -> Option<Self> {
Some(Self(value.clone()))
fn parse(value: &Value) -> InputValueResult<Self> {
Ok(Self(value.clone()))
}

fn is_valid(_value: &Value) -> bool {
Expand Down
8 changes: 4 additions & 4 deletions src/scalars/bool.rs
@@ -1,4 +1,4 @@
use crate::{Result, ScalarType, Value};
use crate::{InputValueError, InputValueResult, Result, ScalarType, Value};
use async_graphql_derive::Scalar;

#[Scalar(internal)]
Expand All @@ -11,10 +11,10 @@ impl ScalarType for bool {
Some("The `Boolean` scalar type represents `true` or `false`.")
}

fn parse(value: &Value) -> Option<Self> {
fn parse(value: &Value) -> InputValueResult<Self> {
match value {
Value::Boolean(n) => Some(*n),
_ => None,
Value::Boolean(n) => Ok(*n),
_ => Err(InputValueError::ExpectedType),
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/scalars/bson.rs
@@ -1,4 +1,4 @@
use crate::{Result, ScalarType, Value};
use crate::{InputValueError, InputValueResult, Result, ScalarType, Value};
use async_graphql_derive::Scalar;
use bson::{oid::ObjectId, UtcDateTime};
use chrono::{DateTime, Utc};
Expand All @@ -9,10 +9,10 @@ impl ScalarType for ObjectId {
"ObjectId"
}

fn parse(value: &Value) -> Option<Self> {
fn parse(value: &Value) -> InputValueResult<Self> {
match value {
Value::String(s) => Some(ObjectId::with_string(&s).ok()?),
_ => None,
Value::String(s) => Ok(ObjectId::with_string(&s)?),
_ => Err(InputValueError::ExpectedType),
}
}

Expand All @@ -27,7 +27,7 @@ impl ScalarType for UtcDateTime {
"DateTime"
}

fn parse(value: &Value) -> Option<Self> {
fn parse(value: &Value) -> InputValueResult<Self> {
DateTime::<Utc>::parse(value).map(UtcDateTime::from)
}

Expand Down
8 changes: 4 additions & 4 deletions src/scalars/chrono_tz.rs
@@ -1,4 +1,4 @@
use crate::{Result, ScalarType, Value};
use crate::{InputValueError, InputValueResult, Result, ScalarType, Value};
use async_graphql_derive::Scalar;
use chrono_tz::Tz;
use std::str::FromStr;
Expand All @@ -9,10 +9,10 @@ impl ScalarType for Tz {
"TimeZone"
}

fn parse(value: &Value) -> Option<Self> {
fn parse(value: &Value) -> InputValueResult<Self> {
match value {
Value::String(s) => Some(Tz::from_str(&s).ok()?),
_ => None,
Value::String(s) => Ok(Tz::from_str(&s)?),
_ => Err(InputValueError::ExpectedType),
}
}

Expand Down

0 comments on commit 6d7d648

Please sign in to comment.