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/),
|
||||
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.
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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;)*
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
25
src/base.rs
25
src/base.rs
|
@ -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)]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>> {
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue