Allowed use validators on wrapper types, for example: `Option<T>`, `MaybeUnefined<T>`.

Remove `OutputJson` because `Json` can replace it.
This commit is contained in:
Sunli 2021-11-18 15:43:12 +08:00
parent 3616ca8fec
commit 559bbedd3e
22 changed files with 346 additions and 83 deletions

View File

@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.0.4] 2021-11-18
- Remove `OutputJson` because `Json` can replace it.
- Allowed use validators on wrapper types, for example: `Option<T>`, `MaybeUnefined<T>`.
## [3.0.3] 2021-11-18
- [integrations] Make `GraphQLWebSocket::new` use generic stream.

View File

@ -148,6 +148,8 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::InputType for #ident {
type RawValueType = Self;
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
Self::__type_name()
}
@ -163,6 +165,10 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
fn to_value(&self) -> #crate_name::Value {
#crate_name::resolver_utils::enum_value(*self)
}
fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> {
::std::option::Option::Some(self)
}
}
#[#crate_name::async_trait::async_trait]

View File

@ -206,6 +206,8 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::InputType for #ident {
type RawValueType = Self;
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
::std::borrow::Cow::Borrowed(#gql_typename)
}
@ -242,6 +244,10 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
fn federation_fields() -> ::std::option::Option<::std::string::String> {
#get_federation_fields
}
fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> {
::std::option::Option::Some(self)
}
}
impl #crate_name::InputObjectType for #ident {}
@ -295,6 +301,8 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
let expanded = quote! {
#[allow(clippy::all, clippy::pedantic)]
impl #crate_name::InputType for #concrete_type {
type RawValueType = Self;
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
::std::borrow::Cow::Borrowed(#gql_typename)
}
@ -314,6 +322,10 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
fn federation_fields() -> ::std::option::Option<::std::string::String> {
Self::__internal_federation_fields()
}
fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> {
::std::option::Option::Some(self)
}
}
impl #crate_name::InputObjectType for #concrete_type {}

View File

@ -82,6 +82,8 @@ pub fn generate(newtype_args: &args::NewType) -> GeneratorResult<TokenStream> {
#[allow(clippy::all, clippy::pedantic)]
impl #impl_generics #crate_name::InputType for #ident #ty_generics #where_clause {
type RawValueType = #inner_ty;
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
#type_name
}
@ -97,6 +99,10 @@ pub fn generate(newtype_args: &args::NewType) -> GeneratorResult<TokenStream> {
fn to_value(&self) -> #crate_name::Value {
<#ident as #crate_name::ScalarType>::to_value(self)
}
fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> {
self.0.as_raw_value()
}
}
#[allow(clippy::all, clippy::pedantic)]

View File

@ -40,6 +40,8 @@ pub fn generate(
#[allow(clippy::all, clippy::pedantic)]
impl #generic #crate_name::InputType for #self_ty #where_clause {
type RawValueType = Self;
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
::std::borrow::Cow::Borrowed(#gql_typename)
}
@ -61,6 +63,10 @@ pub fn generate(
fn to_value(&self) -> #crate_name::Value {
<#self_ty as #crate_name::ScalarType>::to_value(self)
}
fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> {
::std::option::Option::Some(self)
}
}
#[allow(clippy::all, clippy::pedantic)]

View File

@ -72,111 +72,110 @@ impl Validators {
map_err: Option<TokenStream>,
) -> Result<TokenStream> {
let mut codes = Vec::new();
let mut value = value;
let mut container = None;
if self.list {
container = Some(quote!(#value));
value = quote!(__item);
}
if let Some(n) = &self.multiple_of {
codes.push(quote! {
#crate_name::validators::multiple_of(#value, #n)
#crate_name::validators::multiple_of(__raw_value, #n)
});
}
if let Some(n) = &self.maximum {
codes.push(quote! {
#crate_name::validators::maximum(#value, #n)
#crate_name::validators::maximum(__raw_value, #n)
});
}
if let Some(n) = &self.minimum {
codes.push(quote! {
#crate_name::validators::minimum(#value, #n)
#crate_name::validators::minimum(__raw_value, #n)
});
}
if let Some(n) = &self.max_length {
codes.push(quote! {
#crate_name::validators::max_length(#value, #n)
#crate_name::validators::max_length(__raw_value, #n)
});
}
if let Some(n) = &self.min_length {
codes.push(quote! {
#crate_name::validators::min_length(#value, #n)
#crate_name::validators::min_length(__raw_value, #n)
});
}
if let Some(n) = &self.max_items {
codes.push(quote! {
#crate_name::validators::max_items(#value, #n)
#crate_name::validators::max_items(__raw_value, #n)
});
}
if let Some(n) = &self.min_items {
codes.push(quote! {
#crate_name::validators::min_items(#value, #n)
#crate_name::validators::min_items(__raw_value, #n)
});
}
if let Some(n) = &self.chars_max_length {
codes.push(quote! {
#crate_name::validators::chars_max_length(#value, #n)
#crate_name::validators::chars_max_length(__raw_value, #n)
});
}
if let Some(n) = &self.chars_min_length {
codes.push(quote! {
#crate_name::validators::chars_min_length(#value, #n)
#crate_name::validators::chars_min_length(__raw_value, #n)
});
}
if self.email {
codes.push(quote! {
#crate_name::validators::email(#value)
#crate_name::validators::email(__raw_value)
});
}
if self.url {
codes.push(quote! {
#crate_name::validators::url(#value)
#crate_name::validators::url(__raw_value)
});
}
if self.ip {
codes.push(quote! {
#crate_name::validators::ip(#value)
#crate_name::validators::ip(__raw_value)
});
}
if let Some(re) = &self.regex {
codes.push(quote! {
#crate_name::validators::regex(#value, #re)
#crate_name::validators::regex(__raw_value, #re)
});
}
for s in &self.custom {
let expr: Expr =
let __create_custom_validator: Expr =
syn::parse_str(s).map_err(|err| Error::new(s.span(), err.to_string()))?;
codes.push(quote! {
#crate_name::CustomValidator::check(&(#expr), #value)
#crate_name::CustomValidator::check(&(#__create_custom_validator), __raw_value)
.map_err(|err_msg| #crate_name::InputValueError::<#ty>::custom(err_msg))
});
}
let codes = codes.into_iter().map(|s| quote!(#s #map_err ?));
if let Some(container) = container {
if self.list {
Ok(quote! {
for __item in #container {
#(#codes;)*
for __item in #value {
if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(__item) {
#(#codes;)*
}
}
})
} else {
Ok(quote!(#(#codes;)*))
Ok(quote! {
if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(#value) {
#(#codes;)*
}
})
}
}
}

View File

@ -17,6 +17,16 @@ pub trait Description {
/// Represents a GraphQL input type.
pub trait InputType: Send + Sync + Sized {
/// The raw type used for validator.
///
/// Usually it is `Self`, but the wrapper type is its internal type.
///
/// For example:
///
/// `i32::RawValueType` is `i32`
/// `Option<i32>::RawValueType` is `i32`.
type RawValueType;
/// Type the name.
fn type_name() -> Cow<'static, str>;
@ -39,6 +49,9 @@ pub trait InputType: Send + Sync + Sized {
fn federation_fields() -> Option<String> {
None
}
/// Returns a reference to the raw value.
fn as_raw_value(&self) -> Option<&Self::RawValueType>;
}
/// Represents a GraphQL output type.
@ -151,6 +164,8 @@ impl<T: OutputType + ?Sized> OutputType for Box<T> {
#[async_trait::async_trait]
impl<T: InputType> InputType for Box<T> {
type RawValueType = T::RawValueType;
fn type_name() -> Cow<'static, str> {
T::type_name()
}
@ -168,6 +183,10 @@ impl<T: InputType> InputType for Box<T> {
fn to_value(&self) -> ConstValue {
T::to_value(&self)
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
self.as_ref().as_raw_value()
}
}
#[async_trait::async_trait]
@ -191,6 +210,8 @@ impl<T: OutputType + ?Sized> OutputType for Arc<T> {
}
impl<T: InputType> InputType for Arc<T> {
type RawValueType = T::RawValueType;
fn type_name() -> Cow<'static, str> {
T::type_name()
}
@ -208,6 +229,10 @@ impl<T: InputType> InputType for Arc<T> {
fn to_value(&self) -> ConstValue {
T::to_value(&self)
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
self.as_ref().as_raw_value()
}
}
#[doc(hidden)]

View File

@ -147,6 +147,8 @@ macro_rules! scalar_internal {
}
impl $crate::InputType for $ty {
type RawValueType = Self;
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
::std::borrow::Cow::Borrowed($name)
}
@ -172,6 +174,10 @@ macro_rules! scalar_internal {
fn to_value(&self) -> $crate::Value {
<$ty as $crate::ScalarType>::to_value(self)
}
fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> {
::std::option::Option::Some(self)
}
}
#[$crate::async_trait::async_trait]

View File

@ -8,6 +8,8 @@ use crate::{
};
impl<T: InputType, const N: usize> InputType for [T; N] {
type RawValueType = Self;
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("[{}]", T::qualified_type_name()))
}
@ -46,6 +48,10 @@ impl<T: InputType, const N: usize> InputType for [T; N] {
fn to_value(&self) -> Value {
Value::List(self.iter().map(InputType::to_value).collect())
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(self)
}
}
#[async_trait::async_trait]

View File

@ -9,6 +9,8 @@ use crate::{
};
impl<T: InputType + Ord> InputType for BTreeSet<T> {
type RawValueType = Self;
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("[{}]", T::qualified_type_name()))
}
@ -40,6 +42,10 @@ impl<T: InputType + Ord> InputType for BTreeSet<T> {
fn to_value(&self) -> Value {
Value::List(self.iter().map(InputType::to_value).collect())
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(self)
}
}
#[async_trait::async_trait]

View File

@ -11,6 +11,8 @@ use crate::{
};
impl<T: InputType + Hash + Eq> InputType for HashSet<T> {
type RawValueType = Self;
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("[{}]", T::qualified_type_name()))
}
@ -42,6 +44,10 @@ impl<T: InputType + Hash + Eq> InputType for HashSet<T> {
fn to_value(&self) -> Value {
Value::List(self.iter().map(InputType::to_value).collect())
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(self)
}
}
#[async_trait::async_trait]

View File

@ -9,6 +9,8 @@ use crate::{
};
impl<T: InputType> InputType for LinkedList<T> {
type RawValueType = Self;
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("[{}]", T::qualified_type_name()))
}
@ -41,6 +43,10 @@ impl<T: InputType> InputType for LinkedList<T> {
fn to_value(&self) -> Value {
Value::List(self.iter().map(InputType::to_value).collect())
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(self)
}
}
#[async_trait::async_trait]

View File

@ -8,6 +8,8 @@ use crate::{
};
impl<T: InputType> InputType for Vec<T> {
type RawValueType = Self;
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("[{}]", T::qualified_type_name()))
}
@ -37,6 +39,10 @@ impl<T: InputType> InputType for Vec<T> {
fn to_value(&self) -> Value {
Value::List(self.iter().map(InputType::to_value).collect())
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(self)
}
}
#[async_trait::async_trait]

View File

@ -9,6 +9,8 @@ use crate::{
};
impl<T: InputType> InputType for VecDeque<T> {
type RawValueType = Self;
fn type_name() -> Cow<'static, str> {
Cow::Owned(format!("[{}]", T::qualified_type_name()))
}
@ -41,6 +43,10 @@ impl<T: InputType> InputType for VecDeque<T> {
fn to_value(&self) -> Value {
Value::List(self.iter().map(InputType::to_value).collect())
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(self)
}
}
#[async_trait::async_trait]

View File

@ -7,6 +7,8 @@ use crate::{
};
impl<T: InputType> InputType for Option<T> {
type RawValueType = T::RawValueType;
fn type_name() -> Cow<'static, str> {
T::type_name()
}
@ -35,6 +37,13 @@ impl<T: InputType> InputType for Option<T> {
None => Value::Null,
}
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
match self {
Some(value) => value.as_raw_value(),
None => None,
}
}
}
#[async_trait::async_trait]

View File

@ -1,10 +1,12 @@
use std::borrow::Cow;
use secrecy::{Secret, Zeroize};
use secrecy::{ExposeSecret, Secret, Zeroize};
use crate::{registry, InputType, InputValueError, InputValueResult, Value};
impl<T: InputType + Zeroize> InputType for Secret<T> {
type RawValueType = T::RawValueType;
fn type_name() -> Cow<'static, str> {
T::type_name()
}
@ -26,4 +28,8 @@ impl<T: InputType + Zeroize> InputType for Secret<T> {
fn to_value(&self) -> Value {
Value::Null
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
self.expose_secret().as_raw_value()
}
}

View File

@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize};
use crate::parser::types::Field;
use crate::registry::{MetaType, Registry};
use crate::{
from_value, to_value, ContextSelectionSet, InputValueError, InputValueResult, OutputType,
Positioned, Scalar, ScalarType, ServerResult, Value,
from_value, to_value, ContextSelectionSet, InputType, InputValueResult, OutputType, Positioned,
ServerResult, Value,
};
/// A scalar that can represent any JSON value.
@ -32,57 +32,51 @@ impl<T> DerefMut for Json<T> {
}
}
impl<T: DeserializeOwned + Serialize> From<T> for Json<T> {
impl<T> From<T> for Json<T> {
fn from(value: T) -> Self {
Self(value)
}
}
/// A scalar that can represent any JSON value.
#[Scalar(internal, name = "JSON")]
impl<T: DeserializeOwned + Serialize + Send + Sync> ScalarType for Json<T> {
fn parse(value: Value) -> InputValueResult<Self> {
Ok(from_value(value)?)
impl<T: DeserializeOwned + Serialize + Send + Sync> InputType for Json<T> {
type RawValueType = T;
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("JSON")
}
fn create_type_info(registry: &mut Registry) -> String {
registry.create_output_type::<Json<T>, _>(|_| MetaType::Scalar {
name: <Self as InputType>::type_name().to_string(),
description: None,
is_valid: |_| true,
visible: None,
specified_by_url: None,
})
}
fn parse(value: Option<Value>) -> InputValueResult<Self> {
Ok(from_value(value.unwrap_or_default())?)
}
fn to_value(&self) -> Value {
to_value(&self.0).unwrap_or_default()
}
}
/// A `Json` type that only implements `OutputType`.
#[derive(Serialize, Clone, Debug, Eq, PartialEq, Hash, Default)]
pub struct OutputJson<T>(pub T);
impl<T> Deref for OutputJson<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for OutputJson<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T: Serialize> From<T> for OutputJson<T> {
fn from(value: T) -> Self {
Self(value)
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(&self.0)
}
}
#[async_trait::async_trait]
impl<T: Serialize + Send + Sync> OutputType for OutputJson<T> {
impl<T: Serialize + Send + Sync> OutputType for Json<T> {
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("Json")
Cow::Borrowed("JSON")
}
fn create_type_info(registry: &mut Registry) -> String {
registry.create_output_type::<OutputJson<T>, _>(|_| MetaType::Scalar {
name: Self::type_name().to_string(),
registry.create_output_type::<Json<T>, _>(|_| MetaType::Scalar {
name: <Self as OutputType>::type_name().to_string(),
description: None,
is_valid: |_| true,
visible: None,
@ -99,20 +93,6 @@ impl<T: Serialize + Send + Sync> OutputType for OutputJson<T> {
}
}
/// A scalar that can represent any JSON value.
#[Scalar(internal, name = "JSON")]
impl ScalarType for serde_json::Value {
fn parse(value: Value) -> InputValueResult<Self> {
value
.into_json()
.map_err(|_| InputValueError::custom("Invalid JSON"))
}
fn to_value(&self) -> Value {
Value::from_json(self.clone()).unwrap_or_default()
}
}
#[cfg(test)]
mod test {
use crate::*;
@ -152,7 +132,7 @@ mod test {
}
#[tokio::test]
async fn test_output_json_type() {
async fn test_json_type_for_serialize_only() {
#[derive(Serialize)]
struct MyStruct {
a: i32,
@ -164,7 +144,7 @@ mod test {
#[Object(internal)]
impl Query {
async fn obj(&self) -> OutputJson<MyStruct> {
async fn obj(&self) -> Json<MyStruct> {
MyStruct {
a: 1,
b: 2,

View File

@ -175,6 +175,8 @@ impl<T> MaybeUndefined<T> {
}
impl<T: InputType> InputType for MaybeUndefined<T> {
type RawValueType = T::RawValueType;
fn type_name() -> Cow<'static, str> {
T::type_name()
}
@ -204,6 +206,14 @@ impl<T: InputType> InputType for MaybeUndefined<T> {
_ => Value::Null,
}
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
if let MaybeUndefined::Value(value) = self {
value.as_raw_value()
} else {
None
}
}
}
impl<T, E> MaybeUndefined<Result<T, E>> {

View File

@ -20,7 +20,7 @@ pub use any::Any;
pub use empty_mutation::EmptyMutation;
pub use empty_subscription::EmptySubscription;
pub use id::ID;
pub use json::{Json, OutputJson};
pub use json::Json;
pub use maybe_undefined::MaybeUndefined;
pub use merged_object::{MergedObject, MergedObjectTail};
#[cfg(feature = "string_number")]

View File

@ -101,6 +101,8 @@ impl Upload {
}
impl InputType for Upload {
type RawValueType = Self;
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("Upload")
}
@ -129,4 +131,8 @@ impl InputType for Upload {
fn to_value(&self) -> Value {
Value::Null
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(self)
}
}

View File

@ -22,12 +22,12 @@ pub async fn test_json_scalar() {
Json(MyData(items))
}
async fn data_output(&self) -> OutputJson<&MyDataOutput> {
OutputJson(&self.data)
async fn data_output(&self) -> Json<&MyDataOutput> {
Json(&self.data)
}
async fn data_output_clone(&self) -> OutputJson<MyDataOutput> {
OutputJson(self.data.clone())
async fn data_output_clone(&self) -> Json<MyDataOutput> {
Json(self.data.clone())
}
}

View File

@ -1,5 +1,6 @@
use async_graphql::*;
use futures_util::{Stream, StreamExt};
use std::sync::Arc;
#[tokio::test]
pub async fn test_all_validator() {
@ -120,6 +121,8 @@ pub async fn test_validator_on_input_object_field() {
struct MyInput {
#[graphql(validator(maximum = 10))]
a: i32,
#[graphql(validator(maximum = 10))]
b: Option<i32>,
}
struct Query;
@ -127,7 +130,7 @@ pub async fn test_validator_on_input_object_field() {
#[Object]
impl Query {
async fn value(&self, input: MyInput) -> i32 {
input.a
input.a + input.b.unwrap_or_default()
}
}
@ -142,6 +145,17 @@ pub async fn test_validator_on_input_object_field() {
value!({ "value": 5 })
);
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
assert_eq!(
schema
.execute("{ value(input: {a: 5, b: 7}) }")
.await
.into_result()
.unwrap()
.data,
value!({ "value": 12 })
);
assert_eq!(
schema
.execute("{ value(input: {a: 11}) }")
@ -160,6 +174,25 @@ pub async fn test_validator_on_input_object_field() {
extensions: None
}]
);
assert_eq!(
schema
.execute("{ value(input: {a: 5, b: 20}) }")
.await
.into_result()
.unwrap_err(),
vec![ServerError {
message: r#"Failed to parse "Int": the value is 20, must be less than or equal to 10 (occurred while parsing "MyInput")"#
.to_string(),
source: None,
locations: vec![Pos {
line: 1,
column: 16
}],
path: vec![PathSegment::Field("value".to_string())],
extensions: None
}]
);
}
#[tokio::test]
@ -454,3 +487,121 @@ pub async fn test_list_validator() {
}]
);
}
#[tokio::test]
pub async fn test_validate_wrapper_types() {
#[derive(NewType)]
struct Size(i32);
struct Query;
#[Object]
impl Query {
async fn a(&self, #[graphql(validator(maximum = 10))] n: Option<i32>) -> i32 {
n.unwrap_or_default()
}
async fn b(&self, #[graphql(validator(maximum = 10))] n: Option<Option<i32>>) -> i32 {
n.unwrap_or_default().unwrap_or_default()
}
async fn c(&self, #[graphql(validator(maximum = 10))] n: MaybeUndefined<i32>) -> i32 {
n.take().unwrap_or_default()
}
async fn d(&self, #[graphql(validator(maximum = 10))] n: Box<i32>) -> i32 {
*n
}
async fn e(&self, #[graphql(validator(maximum = 10))] n: Arc<i32>) -> i32 {
*n
}
async fn f(&self, #[graphql(validator(maximum = 10))] n: Json<i32>) -> i32 {
n.0
}
async fn g(&self, #[graphql(validator(maximum = 10))] n: Option<Json<i32>>) -> i32 {
n.map(|n| n.0).unwrap_or_default()
}
async fn h(&self, #[graphql(validator(maximum = 10))] n: Size) -> i32 {
n.0
}
async fn i(&self, #[graphql(validator(list, maximum = 10))] n: Vec<i32>) -> i32 {
n.into_iter().sum()
}
}
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
let successes = [
("{ a(n: 5) }", value!({ "a": 5 })),
("{ a }", value!({ "a": 0 })),
("{ b(n: 5) }", value!({ "b": 5 })),
("{ b }", value!({ "b": 0 })),
("{ c(n: 5) }", value!({ "c": 5 })),
("{ c(n: null) }", value!({ "c": 0 })),
("{ c }", value!({ "c": 0 })),
("{ d(n: 5) }", value!({ "d": 5 })),
("{ e(n: 5) }", value!({ "e": 5 })),
("{ f(n: 5) }", value!({ "f": 5 })),
("{ g(n: 5) }", value!({ "g": 5 })),
("{ g }", value!({ "g": 0 })),
("{ h(n: 5) }", value!({ "h": 5 })),
("{ i(n: [1, 2, 3]) }", value!({ "i": 6 })),
];
for (query, res) in successes {
assert_eq!(schema.execute(query).await.into_result().unwrap().data, res);
}
assert_eq!(
schema
.execute("{ a(n:20) }")
.await
.into_result()
.unwrap_err(),
vec![ServerError {
message: r#"Failed to parse "Int": the value is 20, must be less than or equal to 10"#
.to_string(),
source: None,
locations: vec![Pos { line: 1, column: 7 }],
path: vec![PathSegment::Field("a".to_string())],
extensions: None
}]
);
assert_eq!(
schema
.execute("{ b(n:20) }")
.await
.into_result()
.unwrap_err(),
vec![ServerError {
message: r#"Failed to parse "Int": the value is 20, must be less than or equal to 10"#
.to_string(),
source: None,
locations: vec![Pos { line: 1, column: 7 }],
path: vec![PathSegment::Field("b".to_string())],
extensions: None
}]
);
assert_eq!(
schema
.execute("{ f(n:20) }")
.await
.into_result()
.unwrap_err(),
vec![ServerError {
message: r#"Failed to parse "Int": the value is 20, must be less than or equal to 10"#
.to_string(),
source: None,
locations: vec![Pos { line: 1, column: 7 }],
path: vec![PathSegment::Field("f".to_string())],
extensions: None
}]
);
}