Add ability to distinguish between implicit and explicit null (#795)
* add Nullable to distinguish between implicit and explicit null * cargo fmt * add page to book * address comment
This commit is contained in:
parent
61d1365b15
commit
cd66bdb450
13 changed files with 576 additions and 33 deletions
|
@ -29,6 +29,7 @@
|
|||
|
||||
- [Introspection](advanced/introspection.md)
|
||||
- [Non-struct objects](advanced/non_struct_objects.md)
|
||||
- [Implicit and explicit null](advanced/implicit_and_explicit_null.md)
|
||||
- [Objects and generics](advanced/objects_and_generics.md)
|
||||
- [Multiple operations per request](advanced/multiple_ops_per_request.md)
|
||||
- [Dataloaders](advanced/dataloaders.md)
|
||||
|
|
116
docs/book/content/advanced/implicit_and_explicit_null.md
Normal file
116
docs/book/content/advanced/implicit_and_explicit_null.md
Normal file
|
@ -0,0 +1,116 @@
|
|||
# Implicit and explicit null
|
||||
|
||||
There are two ways that a client can submit a null argument or field in a query.
|
||||
|
||||
They can use a null literal:
|
||||
|
||||
```graphql
|
||||
{
|
||||
field(arg: null)
|
||||
}
|
||||
```
|
||||
|
||||
Or they can simply omit the argument:
|
||||
|
||||
```graphql
|
||||
{
|
||||
field
|
||||
}
|
||||
```
|
||||
|
||||
The former is an explicit null and the latter is an implicit null.
|
||||
|
||||
There are some situations where it's useful to know which one the user provided.
|
||||
|
||||
For example, let's say your business logic has a function that allows users to
|
||||
perform a "patch" operation on themselves. Let's say your users can optionally
|
||||
have favorite and least favorite numbers, and the input for that might look
|
||||
like this:
|
||||
|
||||
```rust
|
||||
/// Updates user attributes. Fields that are `None` are left as-is.
|
||||
pub struct UserPatch {
|
||||
/// If `Some`, updates the user's favorite number.
|
||||
pub favorite_number: Option<Option<i32>>,
|
||||
|
||||
/// If `Some`, updates the user's least favorite number.
|
||||
pub least_favorite_number: Option<Option<i32>>,
|
||||
}
|
||||
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
To set a user's favorite number to 7, you would set `favorite_number` to
|
||||
`Some(Some(7))`. In GraphQL, that might look like this:
|
||||
|
||||
```graphql
|
||||
mutation { patchUser(patch: { favoriteNumber: 7 }) }
|
||||
```
|
||||
|
||||
To unset the user's favorite number, you would set `favorite_number` to
|
||||
`Some(None)`. In GraphQL, that might look like this:
|
||||
|
||||
```graphql
|
||||
mutation { patchUser(patch: { favoriteNumber: null }) }
|
||||
```
|
||||
|
||||
If you want to leave the user's favorite number alone, you would set it to
|
||||
`None`. In GraphQL, that might look like this:
|
||||
|
||||
```graphql
|
||||
mutation { patchUser(patch: {}) }
|
||||
```
|
||||
|
||||
The last two cases rely on being able to distinguish between explicit and implicit null.
|
||||
|
||||
In Juniper, this can be done using the `Nullable` type:
|
||||
|
||||
```rust
|
||||
# extern crate juniper;
|
||||
use juniper::{FieldResult, Nullable};
|
||||
|
||||
#[derive(juniper::GraphQLInputObject)]
|
||||
struct UserPatchInput {
|
||||
pub favorite_number: Nullable<i32>,
|
||||
pub least_favorite_number: Nullable<i32>,
|
||||
}
|
||||
|
||||
impl Into<UserPatch> for UserPatchInput {
|
||||
fn into(self) -> UserPatch {
|
||||
UserPatch {
|
||||
// The `explicit` function transforms the `Nullable` into an
|
||||
// `Option<Option<T>>` as expected by the business logic layer.
|
||||
favorite_number: self.favorite_number.explicit(),
|
||||
least_favorite_number: self.least_favorite_number.explicit(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# pub struct UserPatch {
|
||||
# pub favorite_number: Option<Option<i32>>,
|
||||
# pub least_favorite_number: Option<Option<i32>>,
|
||||
# }
|
||||
|
||||
# struct Session;
|
||||
# impl Session {
|
||||
# fn patch_user(&self, _patch: UserPatch) -> FieldResult<()> { Ok(()) }
|
||||
# }
|
||||
|
||||
struct Context {
|
||||
session: Session,
|
||||
}
|
||||
|
||||
struct Mutation;
|
||||
|
||||
#[juniper::graphql_object(Context=Context)]
|
||||
impl Mutation {
|
||||
fn patch_user(ctx: &Context, patch: UserPatchInput) -> FieldResult<bool> {
|
||||
ctx.session.patch_user(patch.into())?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
# fn main() {}
|
||||
```
|
||||
|
||||
This type functions much like `Option`, but has two empty variants so you can
|
||||
distinguish between implicit and explicit null.
|
|
@ -4,7 +4,8 @@ The chapters below cover some more advanced scenarios.
|
|||
|
||||
- [Introspection](introspection.md)
|
||||
- [Non-struct objects](non_struct_objects.md)
|
||||
- [Implicit and explicit null](implicit_and_explicit_null.md)
|
||||
- [Objects and generics](objects_and_generics.md)
|
||||
- [Multiple operations per request](multiple_ops_per_request.md)
|
||||
- [Dataloaders](dataloaders.md)
|
||||
- [Subscriptions](subscriptions.md)
|
||||
- [Subscriptions](subscriptions.md)
|
||||
|
|
81
integration_tests/juniper_tests/src/explicit_null.rs
Normal file
81
integration_tests/juniper_tests/src/explicit_null.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use juniper::*;
|
||||
|
||||
pub struct Context;
|
||||
|
||||
impl juniper::Context for Context {}
|
||||
|
||||
pub struct Query;
|
||||
|
||||
#[derive(juniper::GraphQLInputObject)]
|
||||
struct ObjectInput {
|
||||
field: Nullable<i32>,
|
||||
}
|
||||
|
||||
#[graphql_object(Context=Context)]
|
||||
impl Query {
|
||||
fn is_explicit_null(arg: Nullable<i32>) -> bool {
|
||||
arg.is_explicit_null()
|
||||
}
|
||||
|
||||
fn object_field_is_explicit_null(obj: ObjectInput) -> bool {
|
||||
obj.field.is_explicit_null()
|
||||
}
|
||||
}
|
||||
|
||||
type Schema = juniper::RootNode<'static, Query, EmptyMutation<Context>, EmptySubscription<Context>>;
|
||||
|
||||
#[tokio::test]
|
||||
async fn explicit_null() {
|
||||
let ctx = Context;
|
||||
|
||||
let query = r#"
|
||||
query Foo($emptyObj: ObjectInput!, $literalNullObj: ObjectInput!) {
|
||||
literalOneIsExplicitNull: isExplicitNull(arg: 1)
|
||||
literalNullIsExplicitNull: isExplicitNull(arg: null)
|
||||
noArgIsExplicitNull: isExplicitNull
|
||||
literalOneFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: 1})
|
||||
literalNullFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {field: null})
|
||||
noFieldIsExplicitNull: objectFieldIsExplicitNull(obj: {})
|
||||
emptyVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $emptyObj)
|
||||
literalNullVariableObjectFieldIsExplicitNull: objectFieldIsExplicitNull(obj: $literalNullObj)
|
||||
}
|
||||
"#;
|
||||
|
||||
let (data, errors) = juniper::execute(
|
||||
query,
|
||||
None,
|
||||
&Schema::new(
|
||||
Query,
|
||||
EmptyMutation::<Context>::new(),
|
||||
EmptySubscription::<Context>::new(),
|
||||
),
|
||||
&[
|
||||
("emptyObj".to_string(), InputValue::Object(vec![])),
|
||||
(
|
||||
"literalNullObj".to_string(),
|
||||
InputValue::object(vec![("field", InputValue::null())].into_iter().collect()),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
&ctx,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(errors.len(), 0);
|
||||
assert_eq!(
|
||||
data,
|
||||
graphql_value!({
|
||||
"literalOneIsExplicitNull": false,
|
||||
"literalNullIsExplicitNull": true,
|
||||
"noArgIsExplicitNull": false,
|
||||
"literalOneFieldIsExplicitNull": false,
|
||||
"literalNullFieldIsExplicitNull": true,
|
||||
"noFieldIsExplicitNull": false,
|
||||
"emptyVariableObjectFieldIsExplicitNull": false,
|
||||
"literalNullVariableObjectFieldIsExplicitNull": true,
|
||||
})
|
||||
);
|
||||
}
|
|
@ -5,6 +5,8 @@ mod codegen;
|
|||
#[cfg(test)]
|
||||
mod custom_scalar;
|
||||
#[cfg(test)]
|
||||
mod explicit_null;
|
||||
#[cfg(test)]
|
||||
mod issue_371;
|
||||
#[cfg(test)]
|
||||
mod issue_398;
|
||||
|
|
|
@ -34,6 +34,8 @@
|
|||
- `RuleError`
|
||||
|
||||
- Support `chrono-tz::Tz` scalar behind a `chrono-tz` feature flag. ([#519](https://github.com/graphql-rust/juniper/pull/519))
|
||||
|
||||
- Added support for distinguishing between between implicit and explicit null. ([#795](https://github.com/graphql-rust/juniper/pull/795))
|
||||
|
||||
## Fixes
|
||||
|
||||
|
|
|
@ -153,6 +153,14 @@ pub type Document<'a, S> = Vec<Definition<'a, S>>;
|
|||
pub trait FromInputValue<S = DefaultScalarValue>: Sized {
|
||||
/// Performs the conversion.
|
||||
fn from_input_value(v: &InputValue<S>) -> Option<Self>;
|
||||
|
||||
/// Performs the conversion from an absent value (e.g. to distinguish between implicit and
|
||||
/// explicit null). The default implementation just uses `from_input_value` as if an explicit
|
||||
/// null were provided. This conversion must not fail.
|
||||
fn from_implicit_null() -> Self {
|
||||
Self::from_input_value(&InputValue::<S>::Null)
|
||||
.expect("input value conversion from null must not fail")
|
||||
}
|
||||
}
|
||||
|
||||
/// Losslessly clones a Rust data type into an InputValue.
|
||||
|
|
|
@ -169,6 +169,7 @@ pub use crate::{
|
|||
async_await::{DynGraphQLValueAsync, GraphQLTypeAsync, GraphQLValueAsync},
|
||||
base::{Arguments, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind},
|
||||
marker::{self, GraphQLInterface, GraphQLUnion},
|
||||
nullable::Nullable,
|
||||
scalars::{EmptyMutation, EmptySubscription, ID},
|
||||
subscriptions::{
|
||||
ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue,
|
||||
|
|
|
@ -91,8 +91,6 @@ where
|
|||
if !args.contains_key(arg.name.as_str()) || args[arg.name.as_str()].is_null() {
|
||||
if let Some(ref default_value) = arg.default_value {
|
||||
args.insert(arg.name.as_str(), default_value.clone());
|
||||
} else {
|
||||
args.insert(arg.name.as_str(), InputValue::null());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ pub mod base;
|
|||
pub mod containers;
|
||||
pub mod marker;
|
||||
pub mod name;
|
||||
pub mod nullable;
|
||||
pub mod pointers;
|
||||
pub mod scalars;
|
||||
pub mod subscriptions;
|
||||
|
|
328
juniper/src/types/nullable.rs
Normal file
328
juniper/src/types/nullable.rs
Normal file
|
@ -0,0 +1,328 @@
|
|||
use crate::{
|
||||
ast::{FromInputValue, InputValue, Selection, ToInputValue},
|
||||
executor::{ExecutionResult, Executor, Registry},
|
||||
schema::meta::MetaType,
|
||||
types::{
|
||||
async_await::GraphQLValueAsync,
|
||||
base::{GraphQLType, GraphQLValue},
|
||||
marker::IsInputType,
|
||||
},
|
||||
value::{ScalarValue, Value},
|
||||
};
|
||||
|
||||
/// `Nullable` can be used in situations where you need to distinguish between an implicitly and
|
||||
/// explicitly null input value.
|
||||
///
|
||||
/// The GraphQL spec states that these two field calls are similar, but are not identical:
|
||||
///
|
||||
/// ```graphql
|
||||
/// {
|
||||
/// field(arg: null)
|
||||
/// field
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The first has explicitly provided null to the argument “arg”, while the second has implicitly
|
||||
/// not provided a value to the argument “arg”. These two forms may be interpreted differently. For
|
||||
/// example, a mutation representing deleting a field vs not altering a field, respectively.
|
||||
///
|
||||
/// In cases where you do not need to be able to distinguish between the two types of null, you
|
||||
/// should simply use `Option<T>`.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum Nullable<T> {
|
||||
/// No value
|
||||
ImplicitNull,
|
||||
/// No value, explicitly specified to be null
|
||||
ExplicitNull,
|
||||
/// Some value `T`
|
||||
Some(T),
|
||||
}
|
||||
|
||||
impl<T> Default for Nullable<T> {
|
||||
fn default() -> Self {
|
||||
Self::ImplicitNull
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Nullable<T> {
|
||||
/// Returns `true` if the nullable is a `ExplicitNull` value.
|
||||
#[inline]
|
||||
pub fn is_explicit_null(&self) -> bool {
|
||||
match self {
|
||||
Self::ExplicitNull => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the nullable is a `ImplicitNull` value.
|
||||
#[inline]
|
||||
pub fn is_implicit_null(&self) -> bool {
|
||||
match self {
|
||||
Self::ImplicitNull => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the nullable is a `Some` value.
|
||||
#[inline]
|
||||
pub fn is_some(&self) -> bool {
|
||||
match self {
|
||||
Self::Some(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the nullable is not a `Some` value.
|
||||
#[inline]
|
||||
pub fn is_null(&self) -> bool {
|
||||
match self {
|
||||
Self::Some(_) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts from `&mut Nullable<T>` to `Nullable<&mut T>`.
|
||||
#[inline]
|
||||
pub fn as_mut(&mut self) -> Nullable<&mut T> {
|
||||
match *self {
|
||||
Self::Some(ref mut x) => Nullable::Some(x),
|
||||
Self::ImplicitNull => Nullable::ImplicitNull,
|
||||
Self::ExplicitNull => Nullable::ExplicitNull,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the contained `Some` value, consuming the `self` value.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the value is not a `Some` with a custom panic message provided by `msg`.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn expect(self, msg: &str) -> T {
|
||||
self.some().expect(msg)
|
||||
}
|
||||
|
||||
/// Returns the contained `Some` value or a provided default.
|
||||
#[inline]
|
||||
pub fn unwrap_or(self, default: T) -> T {
|
||||
self.some().unwrap_or(default)
|
||||
}
|
||||
|
||||
/// Returns the contained `Some` value or computes it from a closure.
|
||||
#[inline]
|
||||
pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
|
||||
self.some().unwrap_or_else(f)
|
||||
}
|
||||
|
||||
/// Maps a `Nullable<T>` to `Nullable<U>` by applying a function to a contained value.
|
||||
#[inline]
|
||||
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Nullable<U> {
|
||||
match self {
|
||||
Self::Some(x) => Nullable::Some(f(x)),
|
||||
Self::ImplicitNull => Nullable::ImplicitNull,
|
||||
Self::ExplicitNull => Nullable::ExplicitNull,
|
||||
}
|
||||
}
|
||||
|
||||
/// Applies a function to the contained value (if any), or returns the provided default (if
|
||||
/// not).
|
||||
#[inline]
|
||||
pub fn map_or<U, F: FnOnce(T) -> U>(self, default: U, f: F) -> U {
|
||||
self.some().map_or(default, f)
|
||||
}
|
||||
|
||||
/// Applies a function to the contained value (if any), or computes a default (if not).
|
||||
#[inline]
|
||||
pub fn map_or_else<U, D: FnOnce() -> U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U {
|
||||
self.some().map_or_else(default, f)
|
||||
}
|
||||
|
||||
/// Transforms the `Nullable<T>` into a `Result<T, E>`, mapping `Some(v)` to `Ok(v)` and
|
||||
/// `ImplicitNull` or `ExplicitNull` to `Err(err)`.
|
||||
#[inline]
|
||||
pub fn ok_or<E>(self, err: E) -> Result<T, E> {
|
||||
self.some().ok_or(err)
|
||||
}
|
||||
|
||||
/// Transforms the `Nullable<T>` into a `Result<T, E>`, mapping `Some(v)` to `Ok(v)` and
|
||||
/// `ImplicitNull` or `ExplicitNull` to `Err(err())`.
|
||||
#[inline]
|
||||
pub fn ok_or_else<E, F: FnOnce() -> E>(self, err: F) -> Result<T, E> {
|
||||
self.some().ok_or_else(err)
|
||||
}
|
||||
|
||||
/// Returns the nullable if it contains a value, otherwise returns `b`.
|
||||
#[inline]
|
||||
pub fn or(self, b: Self) -> Self {
|
||||
match self {
|
||||
Self::Some(_) => self,
|
||||
_ => b,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the nullable if it contains a value, otherwise calls `f` and
|
||||
/// returns the result.
|
||||
#[inline]
|
||||
pub fn or_else<F: FnOnce() -> Nullable<T>>(self, f: F) -> Nullable<T> {
|
||||
match self {
|
||||
Self::Some(_) => self,
|
||||
_ => f(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces the actual value in the nullable by the value given in parameter, returning the
|
||||
/// old value if present, leaving a `Some` in its place without deinitializing either one.
|
||||
#[inline]
|
||||
pub fn replace(&mut self, value: T) -> Self {
|
||||
std::mem::replace(self, Self::Some(value))
|
||||
}
|
||||
|
||||
/// Converts from `Nullable<T>` to `Option<T>`.
|
||||
pub fn some(self) -> Option<T> {
|
||||
match self {
|
||||
Self::Some(v) => Some(v),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts from `Nullable<T>` to `Option<Option<T>>`, mapping `Some(v)` to `Some(Some(v))`,
|
||||
/// `ExplicitNull` to `Some(None)`, and `ImplicitNull` to `None`.
|
||||
pub fn explicit(self) -> Option<Option<T>> {
|
||||
match self {
|
||||
Self::Some(v) => Some(Some(v)),
|
||||
Self::ExplicitNull => Some(None),
|
||||
Self::ImplicitNull => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Nullable<&T> {
|
||||
/// Maps a `Nullable<&T>` to a `Nullable<T>` by copying the contents of the nullable.
|
||||
pub fn copied(self) -> Nullable<T> {
|
||||
self.map(|&t| t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Nullable<&mut T> {
|
||||
/// Maps a `Nullable<&mut T>` to a `Nullable<T>` by copying the contents of the nullable.
|
||||
pub fn copied(self) -> Nullable<T> {
|
||||
self.map(|&mut t| t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Nullable<&T> {
|
||||
/// Maps a `Nullable<&T>` to a `Nullable<T>` by cloning the contents of the nullable.
|
||||
pub fn cloned(self) -> Nullable<T> {
|
||||
self.map(|t| t.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Nullable<&mut T> {
|
||||
/// Maps a `Nullable<&mut T>` to a `Nullable<T>` by cloning the contents of the nullable.
|
||||
pub fn cloned(self) -> Nullable<T> {
|
||||
self.map(|t| t.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> GraphQLType<S> for Nullable<T>
|
||||
where
|
||||
T: GraphQLType<S>,
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn name(_: &Self::TypeInfo) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn meta<'r>(info: &Self::TypeInfo, registry: &mut Registry<'r, S>) -> MetaType<'r, S>
|
||||
where
|
||||
S: 'r,
|
||||
{
|
||||
registry.build_nullable_type::<T>(info).into_meta()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> GraphQLValue<S> for Nullable<T>
|
||||
where
|
||||
S: ScalarValue,
|
||||
T: GraphQLValue<S>,
|
||||
{
|
||||
type Context = T::Context;
|
||||
type TypeInfo = T::TypeInfo;
|
||||
|
||||
fn type_name(&self, _: &Self::TypeInfo) -> Option<&'static str> {
|
||||
None
|
||||
}
|
||||
|
||||
fn resolve(
|
||||
&self,
|
||||
info: &Self::TypeInfo,
|
||||
_: Option<&[Selection<S>]>,
|
||||
executor: &Executor<Self::Context, S>,
|
||||
) -> ExecutionResult<S> {
|
||||
match *self {
|
||||
Self::Some(ref obj) => executor.resolve(info, obj),
|
||||
_ => Ok(Value::null()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> GraphQLValueAsync<S> for Nullable<T>
|
||||
where
|
||||
T: GraphQLValueAsync<S>,
|
||||
T::TypeInfo: Sync,
|
||||
T::Context: Sync,
|
||||
S: ScalarValue + Send + Sync,
|
||||
{
|
||||
fn resolve_async<'a>(
|
||||
&'a self,
|
||||
info: &'a Self::TypeInfo,
|
||||
_: Option<&'a [Selection<S>]>,
|
||||
executor: &'a Executor<Self::Context, S>,
|
||||
) -> crate::BoxFuture<'a, ExecutionResult<S>> {
|
||||
let f = async move {
|
||||
let value = match self {
|
||||
Self::Some(obj) => executor.resolve_into_value_async(info, obj).await,
|
||||
_ => Value::null(),
|
||||
};
|
||||
Ok(value)
|
||||
};
|
||||
Box::pin(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> FromInputValue<S> for Nullable<T>
|
||||
where
|
||||
T: FromInputValue<S>,
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn from_input_value(v: &InputValue<S>) -> Option<Nullable<T>> {
|
||||
match v {
|
||||
&InputValue::Null => Some(Self::ExplicitNull),
|
||||
v => v.convert().map(Self::Some),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_implicit_null() -> Self {
|
||||
Self::ImplicitNull
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> ToInputValue<S> for Nullable<T>
|
||||
where
|
||||
T: ToInputValue<S>,
|
||||
S: ScalarValue,
|
||||
{
|
||||
fn to_input_value(&self) -> InputValue<S> {
|
||||
match *self {
|
||||
Self::Some(ref v) => v.to_input_value(),
|
||||
_ => InputValue::null(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> IsInputType<S> for Nullable<T>
|
||||
where
|
||||
T: IsInputType<S>,
|
||||
S: ScalarValue,
|
||||
{
|
||||
}
|
|
@ -46,6 +46,13 @@ fn create(
|
|||
|
||||
let top_attrs = &_impl.attrs;
|
||||
|
||||
let scalar = _impl
|
||||
.attrs
|
||||
.scalar
|
||||
.as_ref()
|
||||
.map(|s| quote!( #s ))
|
||||
.unwrap_or_else(|| quote!(::juniper::DefaultScalarValue));
|
||||
|
||||
let fields = _impl
|
||||
.methods
|
||||
.iter()
|
||||
|
@ -87,10 +94,6 @@ fn create(
|
|||
.apply(&arg_name)
|
||||
});
|
||||
|
||||
let expect_text = format!(
|
||||
"Internal error: missing argument {} - validation must have failed",
|
||||
&final_name
|
||||
);
|
||||
let mut_modifier = if is_mut { quote!(mut) } else { quote!() };
|
||||
|
||||
if final_name.starts_with("__") {
|
||||
|
@ -109,7 +112,7 @@ fn create(
|
|||
let resolver = quote!(
|
||||
let #mut_modifier #arg_ident = args
|
||||
.get::<#ty>(#final_name)
|
||||
.expect(#expect_text);
|
||||
.unwrap_or_else(::juniper::FromInputValue::<#scalar>::from_implicit_null);
|
||||
);
|
||||
|
||||
let field_type = util::GraphQLTypeDefinitionFieldArg {
|
||||
|
|
|
@ -1701,34 +1701,35 @@ impl GraphQLTypeDefiniton {
|
|||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let from_inputs = self.fields.iter().map(|field| {
|
||||
let field_ident = &field.resolver_code;
|
||||
let field_name = &field.name;
|
||||
let from_inputs = self
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let field_ident = &field.resolver_code;
|
||||
let field_name = &field.name;
|
||||
|
||||
// Build from_input clause.
|
||||
let from_input_default = match field.default {
|
||||
Some(ref def) => {
|
||||
quote! {
|
||||
Some(&&::juniper::InputValue::Null) | None if true => #def,
|
||||
// Build from_input clause.
|
||||
let from_input_default = match field.default {
|
||||
Some(ref def) => {
|
||||
quote! {
|
||||
Some(&&::juniper::InputValue::Null) | None if true => #def,
|
||||
}
|
||||
}
|
||||
}
|
||||
None => quote! {},
|
||||
};
|
||||
None => quote! {},
|
||||
};
|
||||
|
||||
quote!(
|
||||
#field_ident: {
|
||||
// TODO: investigate the unwraps here, they seem dangerous!
|
||||
match obj.get(#field_name) {
|
||||
#from_input_default
|
||||
Some(ref v) => ::juniper::FromInputValue::from_input_value(v).unwrap(),
|
||||
None => {
|
||||
::juniper::FromInputValue::from_input_value(&::juniper::InputValue::<#scalar>::null())
|
||||
.unwrap()
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
}).collect::<Vec<_>>();
|
||||
quote!(
|
||||
#field_ident: {
|
||||
// TODO: investigate the unwraps here, they seem dangerous!
|
||||
match obj.get(#field_name) {
|
||||
#from_input_default
|
||||
Some(ref v) => ::juniper::FromInputValue::from_input_value(v).unwrap(),
|
||||
None => ::juniper::FromInputValue::<#scalar>::from_implicit_null(),
|
||||
}
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let to_inputs = self
|
||||
.fields
|
||||
|
|
Loading…
Reference in a new issue