parent
3cdf109bfd
commit
43c8daa132
|
@ -156,8 +156,8 @@ pub fn generate(enum_args: &args::Enum, input: &DeriveInput) -> Result<TokenStre
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #crate_name::InputValueType for #ident {
|
impl #crate_name::InputValueType for #ident {
|
||||||
fn parse(value: #crate_name::Value) -> #crate_name::InputValueResult<Self> {
|
fn parse(value: Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> {
|
||||||
#crate_name::EnumType::parse_enum(value)
|
#crate_name::EnumType::parse_enum(value.unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_value(&self) -> #crate_name::Value {
|
fn to_value(&self) -> #crate_name::Value {
|
||||||
|
|
|
@ -80,14 +80,14 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
|
||||||
get_fields.push(quote! {
|
get_fields.push(quote! {
|
||||||
let #ident: #ty = {
|
let #ident: #ty = {
|
||||||
match obj.get(#name) {
|
match obj.get(#name) {
|
||||||
Some(value) => #crate_name::InputValueType::parse(value.clone())?,
|
Some(value) => #crate_name::InputValueType::parse(Some(value.clone()))?,
|
||||||
None => #default,
|
None => #default,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
get_fields.push(quote! {
|
get_fields.push(quote! {
|
||||||
let #ident:#ty = #crate_name::InputValueType::parse(obj.get(#name).cloned().unwrap_or(#crate_name::Value::Null))?;
|
let #ident:#ty = #crate_name::InputValueType::parse(obj.get(#name).cloned())?;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,14 +129,14 @@ pub fn generate(object_args: &args::InputObject, input: &DeriveInput) -> Result<
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #crate_name::InputValueType for #ident {
|
impl #crate_name::InputValueType for #ident {
|
||||||
fn parse(value: #crate_name::Value) -> #crate_name::InputValueResult<Self> {
|
fn parse(value: Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> {
|
||||||
use #crate_name::Type;
|
use #crate_name::Type;
|
||||||
|
|
||||||
if let #crate_name::Value::Object(obj) = &value {
|
if let Some(#crate_name::Value::Object(obj)) = value {
|
||||||
#(#get_fields)*
|
#(#get_fields)*
|
||||||
Ok(Self { #(#fields),* })
|
Ok(Self { #(#fields),* })
|
||||||
} else {
|
} else {
|
||||||
Err(#crate_name::InputValueError::ExpectedType(value))
|
Err(#crate_name::InputValueError::ExpectedType(value.unwrap_or_default()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,7 @@ pub fn generate(object_args: &args::Object, item_impl: &mut ItemImpl) -> Result<
|
||||||
});
|
});
|
||||||
key_getter.push(quote! {
|
key_getter.push(quote! {
|
||||||
params.get(#name).and_then(|value| {
|
params.get(#name).and_then(|value| {
|
||||||
let value: Option<#ty> = #crate_name::InputValueType::parse(value.clone()).ok();
|
let value: Option<#ty> = #crate_name::InputValueType::parse(Some(value.clone())).ok();
|
||||||
value
|
value
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -47,8 +47,8 @@ pub fn generate(scalar_args: &args::Scalar, item_impl: &mut ItemImpl) -> Result<
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #generic #crate_name::InputValueType for #self_ty #where_clause {
|
impl #generic #crate_name::InputValueType for #self_ty #where_clause {
|
||||||
fn parse(value: #crate_name::Value) -> #crate_name::InputValueResult<Self> {
|
fn parse(value: Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> {
|
||||||
<#self_ty as #crate_name::ScalarType>::parse(value)
|
<#self_ty as #crate_name::ScalarType>::parse(value.unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_value(&self) -> #crate_name::Value {
|
fn to_value(&self) -> #crate_name::Value {
|
||||||
|
|
|
@ -41,6 +41,12 @@ pub enum Value {
|
||||||
Upload(UploadValue),
|
Upload(UploadValue),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Value {
|
||||||
|
fn default() -> Self {
|
||||||
|
Value::Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PartialEq for Value {
|
impl PartialEq for Value {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
use Value::*;
|
use Value::*;
|
||||||
|
|
|
@ -34,8 +34,8 @@ pub trait Type {
|
||||||
|
|
||||||
/// Represents a GraphQL input value
|
/// Represents a GraphQL input value
|
||||||
pub trait InputValueType: Type + Sized {
|
pub trait InputValueType: Type + Sized {
|
||||||
/// Parse from `Value`
|
/// Parse from `Value`,None represent undefined.
|
||||||
fn parse(value: Value) -> InputValueResult<Self>;
|
fn parse(value: Option<Value>) -> InputValueResult<Self>;
|
||||||
|
|
||||||
/// Convert to `Value` for introspection
|
/// Convert to `Value` for introspection
|
||||||
fn to_value(&self) -> Value;
|
fn to_value(&self) -> Value;
|
||||||
|
|
|
@ -459,9 +459,9 @@ impl<'a, T> ContextBase<'a, T> {
|
||||||
for directive in directives {
|
for directive in directives {
|
||||||
if directive.name.node == "skip" {
|
if directive.name.node == "skip" {
|
||||||
if let Some(value) = directive.get_argument("if") {
|
if let Some(value) = directive.get_argument("if") {
|
||||||
match InputValueType::parse(
|
match InputValueType::parse(Some(
|
||||||
self.resolve_input_value(value.clone_inner(), value.position())?,
|
self.resolve_input_value(value.clone_inner(), value.position())?,
|
||||||
) {
|
)) {
|
||||||
Ok(true) => return Ok(true),
|
Ok(true) => return Ok(true),
|
||||||
Ok(false) => {}
|
Ok(false) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -478,9 +478,9 @@ impl<'a, T> ContextBase<'a, T> {
|
||||||
}
|
}
|
||||||
} else if directive.name.node == "include" {
|
} else if directive.name.node == "include" {
|
||||||
if let Some(value) = directive.get_argument("if") {
|
if let Some(value) = directive.get_argument("if") {
|
||||||
match InputValueType::parse(
|
match InputValueType::parse(Some(
|
||||||
self.resolve_input_value(value.clone_inner(), value.position())?,
|
self.resolve_input_value(value.clone_inner(), value.position())?,
|
||||||
) {
|
)) {
|
||||||
Ok(false) => return Ok(true),
|
Ok(false) => return Ok(true),
|
||||||
Ok(true) => {}
|
Ok(true) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -537,20 +537,25 @@ impl<'a> ContextBase<'a, &'a Positioned<Field>> {
|
||||||
name: &str,
|
name: &str,
|
||||||
default: Option<fn() -> T>,
|
default: Option<fn() -> T>,
|
||||||
) -> Result<T> {
|
) -> Result<T> {
|
||||||
match (self.get_argument(name).cloned(), default) {
|
let value = self.get_argument(name).cloned();
|
||||||
(Some(value), _) => {
|
if let Some(default) = default {
|
||||||
let pos = value.position();
|
if value.is_none() {
|
||||||
let value = self.resolve_input_value(value.into_inner(), pos)?;
|
return Ok(default());
|
||||||
match InputValueType::parse(value) {
|
}
|
||||||
|
}
|
||||||
|
let pos = value
|
||||||
|
.as_ref()
|
||||||
|
.map(|value| value.position())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let resolved_value = match value {
|
||||||
|
Some(value) => Some(self.resolve_input_value(value.into_inner(), pos)?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
match InputValueType::parse(resolved_value) {
|
||||||
Ok(res) => Ok(res),
|
Ok(res) => Ok(res),
|
||||||
Err(err) => Err(err.into_error(pos, T::qualified_type_name())),
|
Err(err) => Err(err.into_error(pos, T::qualified_type_name())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(None, Some(default)) => Ok(default()),
|
|
||||||
(None, None) => InputValueType::parse(Value::Null)
|
|
||||||
.map_err(|err| err.into_error(Pos::default(), T::qualified_type_name())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn result_name(&self) -> &str {
|
pub fn result_name(&self) -> &str {
|
||||||
|
|
|
@ -153,7 +153,9 @@ pub use schema::{Schema, SchemaBuilder, SchemaEnv};
|
||||||
pub use subscription::{
|
pub use subscription::{
|
||||||
SimpleBroker, SubscriptionStreams, SubscriptionTransport, WebSocketTransport,
|
SimpleBroker, SubscriptionStreams, SubscriptionTransport, WebSocketTransport,
|
||||||
};
|
};
|
||||||
pub use types::{connection, Deferred, EmptyMutation, EmptySubscription, Streamed, Upload};
|
pub use types::{
|
||||||
|
connection, Deferred, EmptyMutation, EmptySubscription, MaybeUndefined, Streamed, Upload,
|
||||||
|
};
|
||||||
pub use validation::ValidationMode;
|
pub use validation::ValidationMode;
|
||||||
|
|
||||||
/// Result type
|
/// Result type
|
||||||
|
|
|
@ -21,16 +21,16 @@ impl<T: Type> Type for Vec<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InputValueType> InputValueType for Vec<T> {
|
impl<T: InputValueType> InputValueType for Vec<T> {
|
||||||
fn parse(value: Value) -> InputValueResult<Self> {
|
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||||
match value {
|
match value.unwrap_or_default() {
|
||||||
Value::List(values) => {
|
Value::List(values) => {
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
for elem_value in values {
|
for elem_value in values {
|
||||||
result.push(InputValueType::parse(elem_value)?);
|
result.push(InputValueType::parse(Some(elem_value))?);
|
||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
_ => Ok(vec![InputValueType::parse(value)?]),
|
value => Ok(vec![InputValueType::parse(Some(value))?]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
97
src/types/maybe_undefined.rs
Normal file
97
src/types/maybe_undefined.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use crate::{registry, InputValueResult, InputValueType, Type, Value};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
/// Similar to `Option`, but it has three states, `undefined`, `null` and `x`.
|
||||||
|
///
|
||||||
|
/// Spec: https://spec.graphql.org/June2018/#sec-Null-Value
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub enum MaybeUndefined<T> {
|
||||||
|
Undefined,
|
||||||
|
Null,
|
||||||
|
Value(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> MaybeUndefined<T> {
|
||||||
|
/// Returns true if the MaybeUndefined<T> is undefined.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_undefined(&self) -> bool {
|
||||||
|
if let MaybeUndefined::Undefined = self {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the MaybeUndefined<T> is null.
|
||||||
|
#[inline]
|
||||||
|
pub fn is_null(&self) -> bool {
|
||||||
|
if let MaybeUndefined::Null = self {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Borrow the value, returns `None` if the value is `undefined` or `null`, otherwise returns `Some(T)`.
|
||||||
|
#[inline]
|
||||||
|
pub fn value(&self) -> Option<&T> {
|
||||||
|
match self {
|
||||||
|
MaybeUndefined::Value(value) => Some(value),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert MaybeUndefined<T> to Option<T>.
|
||||||
|
#[inline]
|
||||||
|
pub fn take(self) -> Option<T> {
|
||||||
|
match self {
|
||||||
|
MaybeUndefined::Value(value) => Some(value),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Type> Type for MaybeUndefined<T> {
|
||||||
|
fn type_name() -> Cow<'static, str> {
|
||||||
|
T::type_name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn qualified_type_name() -> String {
|
||||||
|
T::type_name().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_type_info(registry: &mut registry::Registry) -> String {
|
||||||
|
T::create_type_info(registry);
|
||||||
|
T::type_name().to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: InputValueType> InputValueType for MaybeUndefined<T> {
|
||||||
|
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||||
|
match value {
|
||||||
|
None => Ok(MaybeUndefined::Undefined),
|
||||||
|
Some(Value::Null) => Ok(MaybeUndefined::Null),
|
||||||
|
Some(value) => Ok(MaybeUndefined::Value(T::parse(Some(value))?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_value(&self) -> Value {
|
||||||
|
match self {
|
||||||
|
MaybeUndefined::Value(value) => value.to_value(),
|
||||||
|
_ => Value::Null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_optional_type() {
|
||||||
|
assert_eq!(MaybeUndefined::<i32>::type_name(), "Int");
|
||||||
|
assert_eq!(MaybeUndefined::<i32>::qualified_type_name(), "Int");
|
||||||
|
assert_eq!(&MaybeUndefined::<i32>::type_name(), "Int");
|
||||||
|
assert_eq!(&MaybeUndefined::<i32>::qualified_type_name(), "Int");
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ mod empty_mutation;
|
||||||
mod empty_subscription;
|
mod empty_subscription;
|
||||||
mod r#enum;
|
mod r#enum;
|
||||||
mod list;
|
mod list;
|
||||||
|
mod maybe_undefined;
|
||||||
mod optional;
|
mod optional;
|
||||||
mod query_root;
|
mod query_root;
|
||||||
mod streamed;
|
mod streamed;
|
||||||
|
@ -13,6 +14,7 @@ mod upload;
|
||||||
pub use deferred::Deferred;
|
pub use deferred::Deferred;
|
||||||
pub use empty_mutation::EmptyMutation;
|
pub use empty_mutation::EmptyMutation;
|
||||||
pub use empty_subscription::EmptySubscription;
|
pub use empty_subscription::EmptySubscription;
|
||||||
|
pub use maybe_undefined::MaybeUndefined;
|
||||||
pub use query_root::QueryRoot;
|
pub use query_root::QueryRoot;
|
||||||
pub use r#enum::{EnumItem, EnumType};
|
pub use r#enum::{EnumItem, EnumType};
|
||||||
pub use streamed::Streamed;
|
pub use streamed::Streamed;
|
||||||
|
|
|
@ -21,10 +21,10 @@ impl<T: Type> Type for Option<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: InputValueType> InputValueType for Option<T> {
|
impl<T: InputValueType> InputValueType for Option<T> {
|
||||||
fn parse(value: Value) -> InputValueResult<Self> {
|
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||||
match value {
|
match value.unwrap_or_default() {
|
||||||
Value::Null => Ok(None),
|
Value::Null => Ok(None),
|
||||||
_ => Ok(Some(T::parse(value)?)),
|
value => Ok(Some(T::parse(Some(value))?)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,8 @@ impl<'a> Type for Upload {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> InputValueType for Upload {
|
impl<'a> InputValueType for Upload {
|
||||||
fn parse(value: Value) -> InputValueResult<Self> {
|
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||||
|
let value = value.unwrap_or_default();
|
||||||
if let Value::Upload(upload) = value {
|
if let Value::Upload(upload) = value {
|
||||||
Ok(Upload(upload))
|
Ok(Upload(upload))
|
||||||
} else {
|
} else {
|
||||||
|
|
57
tests/maybe_undefined.rs
Normal file
57
tests/maybe_undefined.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
use async_graphql::*;
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
pub async fn test_maybe_undefined_type() {
|
||||||
|
#[InputObject]
|
||||||
|
struct MyInput {
|
||||||
|
value: MaybeUndefined<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Query;
|
||||||
|
|
||||||
|
#[Object]
|
||||||
|
impl Query {
|
||||||
|
async fn value1(&self, input: MaybeUndefined<i32>) -> i32 {
|
||||||
|
if input.is_null() {
|
||||||
|
1
|
||||||
|
} else if input.is_undefined() {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
input.take().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn value2(&self, input: MyInput) -> i32 {
|
||||||
|
if input.value.is_null() {
|
||||||
|
1
|
||||||
|
} else if input.value.is_undefined() {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
input.value.take().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
|
||||||
|
let query = r#"
|
||||||
|
{
|
||||||
|
v1:value1(input: 99)
|
||||||
|
v2:value1(input: null)
|
||||||
|
v3:value1()
|
||||||
|
v4:value2(input: { value: 99} )
|
||||||
|
v5:value2(input: { value: null} )
|
||||||
|
v6:value2(input: {} )
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
assert_eq!(
|
||||||
|
schema.execute(&query).await.unwrap().data,
|
||||||
|
serde_json::json!({
|
||||||
|
"v1": 99,
|
||||||
|
"v2": 1,
|
||||||
|
"v3": 2,
|
||||||
|
"v4": 99,
|
||||||
|
"v5": 1,
|
||||||
|
"v6": 2,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user