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 {
|
||||
fn parse(value: #crate_name::Value) -> #crate_name::InputValueResult<Self> {
|
||||
#crate_name::EnumType::parse_enum(value)
|
||||
fn parse(value: Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> {
|
||||
#crate_name::EnumType::parse_enum(value.unwrap_or_default())
|
||||
}
|
||||
|
||||
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! {
|
||||
let #ident: #ty = {
|
||||
match obj.get(#name) {
|
||||
Some(value) => #crate_name::InputValueType::parse(value.clone())?,
|
||||
Some(value) => #crate_name::InputValueType::parse(Some(value.clone()))?,
|
||||
None => #default,
|
||||
}
|
||||
};
|
||||
});
|
||||
} else {
|
||||
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 {
|
||||
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;
|
||||
|
||||
if let #crate_name::Value::Object(obj) = &value {
|
||||
if let Some(#crate_name::Value::Object(obj)) = value {
|
||||
#(#get_fields)*
|
||||
Ok(Self { #(#fields),* })
|
||||
} 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! {
|
||||
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
|
||||
})
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
fn parse(value: #crate_name::Value) -> #crate_name::InputValueResult<Self> {
|
||||
<#self_ty as #crate_name::ScalarType>::parse(value)
|
||||
fn parse(value: Option<#crate_name::Value>) -> #crate_name::InputValueResult<Self> {
|
||||
<#self_ty as #crate_name::ScalarType>::parse(value.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn to_value(&self) -> #crate_name::Value {
|
||||
|
|
|
@ -41,6 +41,12 @@ pub enum Value {
|
|||
Upload(UploadValue),
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Value::Null
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
use Value::*;
|
||||
|
|
|
@ -34,8 +34,8 @@ pub trait Type {
|
|||
|
||||
/// Represents a GraphQL input value
|
||||
pub trait InputValueType: Type + Sized {
|
||||
/// Parse from `Value`
|
||||
fn parse(value: Value) -> InputValueResult<Self>;
|
||||
/// Parse from `Value`,None represent undefined.
|
||||
fn parse(value: Option<Value>) -> InputValueResult<Self>;
|
||||
|
||||
/// Convert to `Value` for introspection
|
||||
fn to_value(&self) -> Value;
|
||||
|
|
|
@ -459,9 +459,9 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
for directive in directives {
|
||||
if directive.name.node == "skip" {
|
||||
if let Some(value) = directive.get_argument("if") {
|
||||
match InputValueType::parse(
|
||||
match InputValueType::parse(Some(
|
||||
self.resolve_input_value(value.clone_inner(), value.position())?,
|
||||
) {
|
||||
)) {
|
||||
Ok(true) => return Ok(true),
|
||||
Ok(false) => {}
|
||||
Err(err) => {
|
||||
|
@ -478,9 +478,9 @@ impl<'a, T> ContextBase<'a, T> {
|
|||
}
|
||||
} else if directive.name.node == "include" {
|
||||
if let Some(value) = directive.get_argument("if") {
|
||||
match InputValueType::parse(
|
||||
match InputValueType::parse(Some(
|
||||
self.resolve_input_value(value.clone_inner(), value.position())?,
|
||||
) {
|
||||
)) {
|
||||
Ok(false) => return Ok(true),
|
||||
Ok(true) => {}
|
||||
Err(err) => {
|
||||
|
@ -537,18 +537,23 @@ impl<'a> ContextBase<'a, &'a Positioned<Field>> {
|
|||
name: &str,
|
||||
default: Option<fn() -> T>,
|
||||
) -> Result<T> {
|
||||
match (self.get_argument(name).cloned(), default) {
|
||||
(Some(value), _) => {
|
||||
let pos = value.position();
|
||||
let value = self.resolve_input_value(value.into_inner(), pos)?;
|
||||
match InputValueType::parse(value) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => Err(err.into_error(pos, T::qualified_type_name())),
|
||||
}
|
||||
let value = self.get_argument(name).cloned();
|
||||
if let Some(default) = default {
|
||||
if value.is_none() {
|
||||
return Ok(default());
|
||||
}
|
||||
(None, Some(default)) => Ok(default()),
|
||||
(None, None) => InputValueType::parse(Value::Null)
|
||||
.map_err(|err| err.into_error(Pos::default(), T::qualified_type_name())),
|
||||
}
|
||||
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),
|
||||
Err(err) => Err(err.into_error(pos, T::qualified_type_name())),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -153,7 +153,9 @@ pub use schema::{Schema, SchemaBuilder, SchemaEnv};
|
|||
pub use subscription::{
|
||||
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;
|
||||
|
||||
/// Result type
|
||||
|
|
|
@ -21,16 +21,16 @@ impl<T: Type> Type for Vec<T> {
|
|||
}
|
||||
|
||||
impl<T: InputValueType> InputValueType for Vec<T> {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||
match value.unwrap_or_default() {
|
||||
Value::List(values) => {
|
||||
let mut result = Vec::new();
|
||||
for elem_value in values {
|
||||
result.push(InputValueType::parse(elem_value)?);
|
||||
result.push(InputValueType::parse(Some(elem_value))?);
|
||||
}
|
||||
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 r#enum;
|
||||
mod list;
|
||||
mod maybe_undefined;
|
||||
mod optional;
|
||||
mod query_root;
|
||||
mod streamed;
|
||||
|
@ -13,6 +14,7 @@ mod upload;
|
|||
pub use deferred::Deferred;
|
||||
pub use empty_mutation::EmptyMutation;
|
||||
pub use empty_subscription::EmptySubscription;
|
||||
pub use maybe_undefined::MaybeUndefined;
|
||||
pub use query_root::QueryRoot;
|
||||
pub use r#enum::{EnumItem, EnumType};
|
||||
pub use streamed::Streamed;
|
||||
|
|
|
@ -21,10 +21,10 @@ impl<T: Type> Type for Option<T> {
|
|||
}
|
||||
|
||||
impl<T: InputValueType> InputValueType for Option<T> {
|
||||
fn parse(value: Value) -> InputValueResult<Self> {
|
||||
match value {
|
||||
fn parse(value: Option<Value>) -> InputValueResult<Self> {
|
||||
match value.unwrap_or_default() {
|
||||
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 {
|
||||
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 {
|
||||
Ok(Upload(upload))
|
||||
} 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