Allowed use validators on wrapper types, for example: Option<T>
, MaybeUnefined<T>
.
Remove `OutputJson` because `Json` can replace it.
This commit is contained in:
parent
3616ca8fec
commit
559bbedd3e
|
@ -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/),
|
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).
|
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
|
## [3.0.3] 2021-11-18
|
||||||
|
|
||||||
- [integrations] Make `GraphQLWebSocket::new` use generic stream.
|
- [integrations] Make `GraphQLWebSocket::new` use generic stream.
|
||||||
|
|
|
@ -148,6 +148,8 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
|
||||||
|
|
||||||
#[allow(clippy::all, clippy::pedantic)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
impl #crate_name::InputType for #ident {
|
impl #crate_name::InputType for #ident {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
||||||
Self::__type_name()
|
Self::__type_name()
|
||||||
}
|
}
|
||||||
|
@ -163,6 +165,10 @@ pub fn generate(enum_args: &args::Enum) -> GeneratorResult<TokenStream> {
|
||||||
fn to_value(&self) -> #crate_name::Value {
|
fn to_value(&self) -> #crate_name::Value {
|
||||||
#crate_name::resolver_utils::enum_value(*self)
|
#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]
|
#[#crate_name::async_trait::async_trait]
|
||||||
|
|
|
@ -206,6 +206,8 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
||||||
quote! {
|
quote! {
|
||||||
#[allow(clippy::all, clippy::pedantic)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
impl #crate_name::InputType for #ident {
|
impl #crate_name::InputType for #ident {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
||||||
::std::borrow::Cow::Borrowed(#gql_typename)
|
::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> {
|
fn federation_fields() -> ::std::option::Option<::std::string::String> {
|
||||||
#get_federation_fields
|
#get_federation_fields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> {
|
||||||
|
::std::option::Option::Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #crate_name::InputObjectType for #ident {}
|
impl #crate_name::InputObjectType for #ident {}
|
||||||
|
@ -295,6 +301,8 @@ pub fn generate(object_args: &args::InputObject) -> GeneratorResult<TokenStream>
|
||||||
let expanded = quote! {
|
let expanded = quote! {
|
||||||
#[allow(clippy::all, clippy::pedantic)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
impl #crate_name::InputType for #concrete_type {
|
impl #crate_name::InputType for #concrete_type {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
||||||
::std::borrow::Cow::Borrowed(#gql_typename)
|
::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> {
|
fn federation_fields() -> ::std::option::Option<::std::string::String> {
|
||||||
Self::__internal_federation_fields()
|
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 {}
|
impl #crate_name::InputObjectType for #concrete_type {}
|
||||||
|
|
|
@ -82,6 +82,8 @@ pub fn generate(newtype_args: &args::NewType) -> GeneratorResult<TokenStream> {
|
||||||
|
|
||||||
#[allow(clippy::all, clippy::pedantic)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
impl #impl_generics #crate_name::InputType for #ident #ty_generics #where_clause {
|
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> {
|
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
||||||
#type_name
|
#type_name
|
||||||
}
|
}
|
||||||
|
@ -97,6 +99,10 @@ pub fn generate(newtype_args: &args::NewType) -> GeneratorResult<TokenStream> {
|
||||||
fn to_value(&self) -> #crate_name::Value {
|
fn to_value(&self) -> #crate_name::Value {
|
||||||
<#ident as #crate_name::ScalarType>::to_value(self)
|
<#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)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
|
|
|
@ -40,6 +40,8 @@ pub fn generate(
|
||||||
|
|
||||||
#[allow(clippy::all, clippy::pedantic)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
impl #generic #crate_name::InputType for #self_ty #where_clause {
|
impl #generic #crate_name::InputType for #self_ty #where_clause {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
||||||
::std::borrow::Cow::Borrowed(#gql_typename)
|
::std::borrow::Cow::Borrowed(#gql_typename)
|
||||||
}
|
}
|
||||||
|
@ -61,6 +63,10 @@ pub fn generate(
|
||||||
fn to_value(&self) -> #crate_name::Value {
|
fn to_value(&self) -> #crate_name::Value {
|
||||||
<#self_ty as #crate_name::ScalarType>::to_value(self)
|
<#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)]
|
#[allow(clippy::all, clippy::pedantic)]
|
||||||
|
|
|
@ -72,111 +72,110 @@ impl Validators {
|
||||||
map_err: Option<TokenStream>,
|
map_err: Option<TokenStream>,
|
||||||
) -> Result<TokenStream> {
|
) -> Result<TokenStream> {
|
||||||
let mut codes = Vec::new();
|
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 {
|
if let Some(n) = &self.multiple_of {
|
||||||
codes.push(quote! {
|
codes.push(quote! {
|
||||||
#crate_name::validators::multiple_of(#value, #n)
|
#crate_name::validators::multiple_of(__raw_value, #n)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(n) = &self.maximum {
|
if let Some(n) = &self.maximum {
|
||||||
codes.push(quote! {
|
codes.push(quote! {
|
||||||
#crate_name::validators::maximum(#value, #n)
|
#crate_name::validators::maximum(__raw_value, #n)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(n) = &self.minimum {
|
if let Some(n) = &self.minimum {
|
||||||
codes.push(quote! {
|
codes.push(quote! {
|
||||||
#crate_name::validators::minimum(#value, #n)
|
#crate_name::validators::minimum(__raw_value, #n)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(n) = &self.max_length {
|
if let Some(n) = &self.max_length {
|
||||||
codes.push(quote! {
|
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 {
|
if let Some(n) = &self.min_length {
|
||||||
codes.push(quote! {
|
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 {
|
if let Some(n) = &self.max_items {
|
||||||
codes.push(quote! {
|
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 {
|
if let Some(n) = &self.min_items {
|
||||||
codes.push(quote! {
|
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 {
|
if let Some(n) = &self.chars_max_length {
|
||||||
codes.push(quote! {
|
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 {
|
if let Some(n) = &self.chars_min_length {
|
||||||
codes.push(quote! {
|
codes.push(quote! {
|
||||||
#crate_name::validators::chars_min_length(#value, #n)
|
#crate_name::validators::chars_min_length(__raw_value, #n)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.email {
|
if self.email {
|
||||||
codes.push(quote! {
|
codes.push(quote! {
|
||||||
#crate_name::validators::email(#value)
|
#crate_name::validators::email(__raw_value)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.url {
|
if self.url {
|
||||||
codes.push(quote! {
|
codes.push(quote! {
|
||||||
#crate_name::validators::url(#value)
|
#crate_name::validators::url(__raw_value)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.ip {
|
if self.ip {
|
||||||
codes.push(quote! {
|
codes.push(quote! {
|
||||||
#crate_name::validators::ip(#value)
|
#crate_name::validators::ip(__raw_value)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(re) = &self.regex {
|
if let Some(re) = &self.regex {
|
||||||
codes.push(quote! {
|
codes.push(quote! {
|
||||||
#crate_name::validators::regex(#value, #re)
|
#crate_name::validators::regex(__raw_value, #re)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
for s in &self.custom {
|
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()))?;
|
syn::parse_str(s).map_err(|err| Error::new(s.span(), err.to_string()))?;
|
||||||
codes.push(quote! {
|
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))
|
.map_err(|err_msg| #crate_name::InputValueError::<#ty>::custom(err_msg))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let codes = codes.into_iter().map(|s| quote!(#s #map_err ?));
|
let codes = codes.into_iter().map(|s| quote!(#s #map_err ?));
|
||||||
|
|
||||||
if let Some(container) = container {
|
if self.list {
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
for __item in #container {
|
for __item in #value {
|
||||||
#(#codes;)*
|
if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(__item) {
|
||||||
|
#(#codes;)*
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Ok(quote!(#(#codes;)*))
|
Ok(quote! {
|
||||||
|
if let ::std::option::Option::Some(__raw_value) = #crate_name::InputType::as_raw_value(#value) {
|
||||||
|
#(#codes;)*
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
25
src/base.rs
25
src/base.rs
|
@ -17,6 +17,16 @@ pub trait Description {
|
||||||
|
|
||||||
/// Represents a GraphQL input type.
|
/// Represents a GraphQL input type.
|
||||||
pub trait InputType: Send + Sync + Sized {
|
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.
|
/// Type the name.
|
||||||
fn type_name() -> Cow<'static, str>;
|
fn type_name() -> Cow<'static, str>;
|
||||||
|
|
||||||
|
@ -39,6 +49,9 @@ pub trait InputType: Send + Sync + Sized {
|
||||||
fn federation_fields() -> Option<String> {
|
fn federation_fields() -> Option<String> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the raw value.
|
||||||
|
fn as_raw_value(&self) -> Option<&Self::RawValueType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a GraphQL output type.
|
/// Represents a GraphQL output type.
|
||||||
|
@ -151,6 +164,8 @@ impl<T: OutputType + ?Sized> OutputType for Box<T> {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<T: InputType> InputType for Box<T> {
|
impl<T: InputType> InputType for Box<T> {
|
||||||
|
type RawValueType = T::RawValueType;
|
||||||
|
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
T::type_name()
|
T::type_name()
|
||||||
}
|
}
|
||||||
|
@ -168,6 +183,10 @@ impl<T: InputType> InputType for Box<T> {
|
||||||
fn to_value(&self) -> ConstValue {
|
fn to_value(&self) -> ConstValue {
|
||||||
T::to_value(&self)
|
T::to_value(&self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
|
||||||
|
self.as_ref().as_raw_value()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
@ -191,6 +210,8 @@ impl<T: OutputType + ?Sized> OutputType for Arc<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InputType> InputType for Arc<T> {
|
impl<T: InputType> InputType for Arc<T> {
|
||||||
|
type RawValueType = T::RawValueType;
|
||||||
|
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
T::type_name()
|
T::type_name()
|
||||||
}
|
}
|
||||||
|
@ -208,6 +229,10 @@ impl<T: InputType> InputType for Arc<T> {
|
||||||
fn to_value(&self) -> ConstValue {
|
fn to_value(&self) -> ConstValue {
|
||||||
T::to_value(&self)
|
T::to_value(&self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
|
||||||
|
self.as_ref().as_raw_value()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
|
|
@ -147,6 +147,8 @@ macro_rules! scalar_internal {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $crate::InputType for $ty {
|
impl $crate::InputType for $ty {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
fn type_name() -> ::std::borrow::Cow<'static, ::std::primitive::str> {
|
||||||
::std::borrow::Cow::Borrowed($name)
|
::std::borrow::Cow::Borrowed($name)
|
||||||
}
|
}
|
||||||
|
@ -172,6 +174,10 @@ macro_rules! scalar_internal {
|
||||||
fn to_value(&self) -> $crate::Value {
|
fn to_value(&self) -> $crate::Value {
|
||||||
<$ty as $crate::ScalarType>::to_value(self)
|
<$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]
|
#[$crate::async_trait::async_trait]
|
||||||
|
|
6
src/types/external/list/array.rs
vendored
6
src/types/external/list/array.rs
vendored
|
@ -8,6 +8,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: InputType, const N: usize> InputType for [T; N] {
|
impl<T: InputType, const N: usize> InputType for [T; N] {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
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 {
|
fn to_value(&self) -> Value {
|
||||||
Value::List(self.iter().map(InputType::to_value).collect())
|
Value::List(self.iter().map(InputType::to_value).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|
6
src/types/external/list/btree_set.rs
vendored
6
src/types/external/list/btree_set.rs
vendored
|
@ -9,6 +9,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: InputType + Ord> InputType for BTreeSet<T> {
|
impl<T: InputType + Ord> InputType for BTreeSet<T> {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||||
}
|
}
|
||||||
|
@ -40,6 +42,10 @@ impl<T: InputType + Ord> InputType for BTreeSet<T> {
|
||||||
fn to_value(&self) -> Value {
|
fn to_value(&self) -> Value {
|
||||||
Value::List(self.iter().map(InputType::to_value).collect())
|
Value::List(self.iter().map(InputType::to_value).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|
6
src/types/external/list/hash_set.rs
vendored
6
src/types/external/list/hash_set.rs
vendored
|
@ -11,6 +11,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: InputType + Hash + Eq> InputType for HashSet<T> {
|
impl<T: InputType + Hash + Eq> InputType for HashSet<T> {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
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 {
|
fn to_value(&self) -> Value {
|
||||||
Value::List(self.iter().map(InputType::to_value).collect())
|
Value::List(self.iter().map(InputType::to_value).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|
6
src/types/external/list/linked_list.rs
vendored
6
src/types/external/list/linked_list.rs
vendored
|
@ -9,6 +9,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: InputType> InputType for LinkedList<T> {
|
impl<T: InputType> InputType for LinkedList<T> {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||||
}
|
}
|
||||||
|
@ -41,6 +43,10 @@ impl<T: InputType> InputType for LinkedList<T> {
|
||||||
fn to_value(&self) -> Value {
|
fn to_value(&self) -> Value {
|
||||||
Value::List(self.iter().map(InputType::to_value).collect())
|
Value::List(self.iter().map(InputType::to_value).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|
6
src/types/external/list/vec.rs
vendored
6
src/types/external/list/vec.rs
vendored
|
@ -8,6 +8,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: InputType> InputType for Vec<T> {
|
impl<T: InputType> InputType for Vec<T> {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||||
}
|
}
|
||||||
|
@ -37,6 +39,10 @@ impl<T: InputType> InputType for Vec<T> {
|
||||||
fn to_value(&self) -> Value {
|
fn to_value(&self) -> Value {
|
||||||
Value::List(self.iter().map(InputType::to_value).collect())
|
Value::List(self.iter().map(InputType::to_value).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|
6
src/types/external/list/vec_deque.rs
vendored
6
src/types/external/list/vec_deque.rs
vendored
|
@ -9,6 +9,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: InputType> InputType for VecDeque<T> {
|
impl<T: InputType> InputType for VecDeque<T> {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
Cow::Owned(format!("[{}]", T::qualified_type_name()))
|
||||||
}
|
}
|
||||||
|
@ -41,6 +43,10 @@ impl<T: InputType> InputType for VecDeque<T> {
|
||||||
fn to_value(&self) -> Value {
|
fn to_value(&self) -> Value {
|
||||||
Value::List(self.iter().map(InputType::to_value).collect())
|
Value::List(self.iter().map(InputType::to_value).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|
9
src/types/external/optional.rs
vendored
9
src/types/external/optional.rs
vendored
|
@ -7,6 +7,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
impl<T: InputType> InputType for Option<T> {
|
impl<T: InputType> InputType for Option<T> {
|
||||||
|
type RawValueType = T::RawValueType;
|
||||||
|
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
T::type_name()
|
T::type_name()
|
||||||
}
|
}
|
||||||
|
@ -35,6 +37,13 @@ impl<T: InputType> InputType for Option<T> {
|
||||||
None => Value::Null,
|
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]
|
#[async_trait::async_trait]
|
||||||
|
|
8
src/types/external/secrecy.rs
vendored
8
src/types/external/secrecy.rs
vendored
|
@ -1,10 +1,12 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use secrecy::{Secret, Zeroize};
|
use secrecy::{ExposeSecret, Secret, Zeroize};
|
||||||
|
|
||||||
use crate::{registry, InputType, InputValueError, InputValueResult, Value};
|
use crate::{registry, InputType, InputValueError, InputValueResult, Value};
|
||||||
|
|
||||||
impl<T: InputType + Zeroize> InputType for Secret<T> {
|
impl<T: InputType + Zeroize> InputType for Secret<T> {
|
||||||
|
type RawValueType = T::RawValueType;
|
||||||
|
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
T::type_name()
|
T::type_name()
|
||||||
}
|
}
|
||||||
|
@ -26,4 +28,8 @@ impl<T: InputType + Zeroize> InputType for Secret<T> {
|
||||||
fn to_value(&self) -> Value {
|
fn to_value(&self) -> Value {
|
||||||
Value::Null
|
Value::Null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
|
||||||
|
self.expose_secret().as_raw_value()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize};
|
||||||
use crate::parser::types::Field;
|
use crate::parser::types::Field;
|
||||||
use crate::registry::{MetaType, Registry};
|
use crate::registry::{MetaType, Registry};
|
||||||
use crate::{
|
use crate::{
|
||||||
from_value, to_value, ContextSelectionSet, InputValueError, InputValueResult, OutputType,
|
from_value, to_value, ContextSelectionSet, InputType, InputValueResult, OutputType, Positioned,
|
||||||
Positioned, Scalar, ScalarType, ServerResult, Value,
|
ServerResult, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A scalar that can represent any JSON 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 {
|
fn from(value: T) -> Self {
|
||||||
Self(value)
|
Self(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A scalar that can represent any JSON value.
|
impl<T: DeserializeOwned + Serialize + Send + Sync> InputType for Json<T> {
|
||||||
#[Scalar(internal, name = "JSON")]
|
type RawValueType = T;
|
||||||
impl<T: DeserializeOwned + Serialize + Send + Sync> ScalarType for Json<T> {
|
|
||||||
fn parse(value: Value) -> InputValueResult<Self> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
Ok(from_value(value)?)
|
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 {
|
fn to_value(&self) -> Value {
|
||||||
to_value(&self.0).unwrap_or_default()
|
to_value(&self.0).unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// A `Json` type that only implements `OutputType`.
|
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
|
||||||
#[derive(Serialize, Clone, Debug, Eq, PartialEq, Hash, Default)]
|
Some(&self.0)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[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> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
Cow::Borrowed("Json")
|
Cow::Borrowed("JSON")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_type_info(registry: &mut Registry) -> String {
|
fn create_type_info(registry: &mut Registry) -> String {
|
||||||
registry.create_output_type::<OutputJson<T>, _>(|_| MetaType::Scalar {
|
registry.create_output_type::<Json<T>, _>(|_| MetaType::Scalar {
|
||||||
name: Self::type_name().to_string(),
|
name: <Self as OutputType>::type_name().to_string(),
|
||||||
description: None,
|
description: None,
|
||||||
is_valid: |_| true,
|
is_valid: |_| true,
|
||||||
visible: None,
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
@ -152,7 +132,7 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_output_json_type() {
|
async fn test_json_type_for_serialize_only() {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct MyStruct {
|
struct MyStruct {
|
||||||
a: i32,
|
a: i32,
|
||||||
|
@ -164,7 +144,7 @@ mod test {
|
||||||
|
|
||||||
#[Object(internal)]
|
#[Object(internal)]
|
||||||
impl Query {
|
impl Query {
|
||||||
async fn obj(&self) -> OutputJson<MyStruct> {
|
async fn obj(&self) -> Json<MyStruct> {
|
||||||
MyStruct {
|
MyStruct {
|
||||||
a: 1,
|
a: 1,
|
||||||
b: 2,
|
b: 2,
|
||||||
|
|
|
@ -175,6 +175,8 @@ impl<T> MaybeUndefined<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InputType> InputType for MaybeUndefined<T> {
|
impl<T: InputType> InputType for MaybeUndefined<T> {
|
||||||
|
type RawValueType = T::RawValueType;
|
||||||
|
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
T::type_name()
|
T::type_name()
|
||||||
}
|
}
|
||||||
|
@ -204,6 +206,14 @@ impl<T: InputType> InputType for MaybeUndefined<T> {
|
||||||
_ => Value::Null,
|
_ => 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>> {
|
impl<T, E> MaybeUndefined<Result<T, E>> {
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub use any::Any;
|
||||||
pub use empty_mutation::EmptyMutation;
|
pub use empty_mutation::EmptyMutation;
|
||||||
pub use empty_subscription::EmptySubscription;
|
pub use empty_subscription::EmptySubscription;
|
||||||
pub use id::ID;
|
pub use id::ID;
|
||||||
pub use json::{Json, OutputJson};
|
pub use json::Json;
|
||||||
pub use maybe_undefined::MaybeUndefined;
|
pub use maybe_undefined::MaybeUndefined;
|
||||||
pub use merged_object::{MergedObject, MergedObjectTail};
|
pub use merged_object::{MergedObject, MergedObjectTail};
|
||||||
#[cfg(feature = "string_number")]
|
#[cfg(feature = "string_number")]
|
||||||
|
|
|
@ -101,6 +101,8 @@ impl Upload {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputType for Upload {
|
impl InputType for Upload {
|
||||||
|
type RawValueType = Self;
|
||||||
|
|
||||||
fn type_name() -> Cow<'static, str> {
|
fn type_name() -> Cow<'static, str> {
|
||||||
Cow::Borrowed("Upload")
|
Cow::Borrowed("Upload")
|
||||||
}
|
}
|
||||||
|
@ -129,4 +131,8 @@ impl InputType for Upload {
|
||||||
fn to_value(&self) -> Value {
|
fn to_value(&self) -> Value {
|
||||||
Value::Null
|
Value::Null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,12 +22,12 @@ pub async fn test_json_scalar() {
|
||||||
Json(MyData(items))
|
Json(MyData(items))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn data_output(&self) -> OutputJson<&MyDataOutput> {
|
async fn data_output(&self) -> Json<&MyDataOutput> {
|
||||||
OutputJson(&self.data)
|
Json(&self.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn data_output_clone(&self) -> OutputJson<MyDataOutput> {
|
async fn data_output_clone(&self) -> Json<MyDataOutput> {
|
||||||
OutputJson(self.data.clone())
|
Json(self.data.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use async_graphql::*;
|
use async_graphql::*;
|
||||||
use futures_util::{Stream, StreamExt};
|
use futures_util::{Stream, StreamExt};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
pub async fn test_all_validator() {
|
pub async fn test_all_validator() {
|
||||||
|
@ -120,6 +121,8 @@ pub async fn test_validator_on_input_object_field() {
|
||||||
struct MyInput {
|
struct MyInput {
|
||||||
#[graphql(validator(maximum = 10))]
|
#[graphql(validator(maximum = 10))]
|
||||||
a: i32,
|
a: i32,
|
||||||
|
#[graphql(validator(maximum = 10))]
|
||||||
|
b: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Query;
|
struct Query;
|
||||||
|
@ -127,7 +130,7 @@ pub async fn test_validator_on_input_object_field() {
|
||||||
#[Object]
|
#[Object]
|
||||||
impl Query {
|
impl Query {
|
||||||
async fn value(&self, input: MyInput) -> i32 {
|
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 })
|
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!(
|
assert_eq!(
|
||||||
schema
|
schema
|
||||||
.execute("{ value(input: {a: 11}) }")
|
.execute("{ value(input: {a: 11}) }")
|
||||||
|
@ -160,6 +174,25 @@ pub async fn test_validator_on_input_object_field() {
|
||||||
extensions: None
|
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]
|
#[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
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user