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)
|
- [Introspection](advanced/introspection.md)
|
||||||
- [Non-struct objects](advanced/non_struct_objects.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)
|
- [Objects and generics](advanced/objects_and_generics.md)
|
||||||
- [Multiple operations per request](advanced/multiple_ops_per_request.md)
|
- [Multiple operations per request](advanced/multiple_ops_per_request.md)
|
||||||
- [Dataloaders](advanced/dataloaders.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,6 +4,7 @@ The chapters below cover some more advanced scenarios.
|
||||||
|
|
||||||
- [Introspection](introspection.md)
|
- [Introspection](introspection.md)
|
||||||
- [Non-struct objects](non_struct_objects.md)
|
- [Non-struct objects](non_struct_objects.md)
|
||||||
|
- [Implicit and explicit null](implicit_and_explicit_null.md)
|
||||||
- [Objects and generics](objects_and_generics.md)
|
- [Objects and generics](objects_and_generics.md)
|
||||||
- [Multiple operations per request](multiple_ops_per_request.md)
|
- [Multiple operations per request](multiple_ops_per_request.md)
|
||||||
- [Dataloaders](dataloaders.md)
|
- [Dataloaders](dataloaders.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)]
|
#[cfg(test)]
|
||||||
mod custom_scalar;
|
mod custom_scalar;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
mod explicit_null;
|
||||||
|
#[cfg(test)]
|
||||||
mod issue_371;
|
mod issue_371;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod issue_398;
|
mod issue_398;
|
||||||
|
|
|
@ -35,6 +35,8 @@
|
||||||
|
|
||||||
- Support `chrono-tz::Tz` scalar behind a `chrono-tz` feature flag. ([#519](https://github.com/graphql-rust/juniper/pull/519))
|
- 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
|
## Fixes
|
||||||
|
|
||||||
- Massively improved the `#[graphql_union]` proc macro. ([#666](https://github.com/graphql-rust/juniper/pull/666)):
|
- Massively improved the `#[graphql_union]` proc macro. ([#666](https://github.com/graphql-rust/juniper/pull/666)):
|
||||||
|
|
|
@ -153,6 +153,14 @@ pub type Document<'a, S> = Vec<Definition<'a, S>>;
|
||||||
pub trait FromInputValue<S = DefaultScalarValue>: Sized {
|
pub trait FromInputValue<S = DefaultScalarValue>: Sized {
|
||||||
/// Performs the conversion.
|
/// Performs the conversion.
|
||||||
fn from_input_value(v: &InputValue<S>) -> Option<Self>;
|
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.
|
/// Losslessly clones a Rust data type into an InputValue.
|
||||||
|
|
|
@ -169,6 +169,7 @@ pub use crate::{
|
||||||
async_await::{DynGraphQLValueAsync, GraphQLTypeAsync, GraphQLValueAsync},
|
async_await::{DynGraphQLValueAsync, GraphQLTypeAsync, GraphQLValueAsync},
|
||||||
base::{Arguments, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind},
|
base::{Arguments, DynGraphQLValue, GraphQLType, GraphQLValue, TypeKind},
|
||||||
marker::{self, GraphQLInterface, GraphQLUnion},
|
marker::{self, GraphQLInterface, GraphQLUnion},
|
||||||
|
nullable::Nullable,
|
||||||
scalars::{EmptyMutation, EmptySubscription, ID},
|
scalars::{EmptyMutation, EmptySubscription, ID},
|
||||||
subscriptions::{
|
subscriptions::{
|
||||||
ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue,
|
ExecutionOutput, GraphQLSubscriptionType, GraphQLSubscriptionValue,
|
||||||
|
|
|
@ -91,8 +91,6 @@ where
|
||||||
if !args.contains_key(arg.name.as_str()) || args[arg.name.as_str()].is_null() {
|
if !args.contains_key(arg.name.as_str()) || args[arg.name.as_str()].is_null() {
|
||||||
if let Some(ref default_value) = arg.default_value {
|
if let Some(ref default_value) = arg.default_value {
|
||||||
args.insert(arg.name.as_str(), default_value.clone());
|
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 containers;
|
||||||
pub mod marker;
|
pub mod marker;
|
||||||
pub mod name;
|
pub mod name;
|
||||||
|
pub mod nullable;
|
||||||
pub mod pointers;
|
pub mod pointers;
|
||||||
pub mod scalars;
|
pub mod scalars;
|
||||||
pub mod subscriptions;
|
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 top_attrs = &_impl.attrs;
|
||||||
|
|
||||||
|
let scalar = _impl
|
||||||
|
.attrs
|
||||||
|
.scalar
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| quote!( #s ))
|
||||||
|
.unwrap_or_else(|| quote!(::juniper::DefaultScalarValue));
|
||||||
|
|
||||||
let fields = _impl
|
let fields = _impl
|
||||||
.methods
|
.methods
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -87,10 +94,6 @@ fn create(
|
||||||
.apply(&arg_name)
|
.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!() };
|
let mut_modifier = if is_mut { quote!(mut) } else { quote!() };
|
||||||
|
|
||||||
if final_name.starts_with("__") {
|
if final_name.starts_with("__") {
|
||||||
|
@ -109,7 +112,7 @@ fn create(
|
||||||
let resolver = quote!(
|
let resolver = quote!(
|
||||||
let #mut_modifier #arg_ident = args
|
let #mut_modifier #arg_ident = args
|
||||||
.get::<#ty>(#final_name)
|
.get::<#ty>(#final_name)
|
||||||
.expect(#expect_text);
|
.unwrap_or_else(::juniper::FromInputValue::<#scalar>::from_implicit_null);
|
||||||
);
|
);
|
||||||
|
|
||||||
let field_type = util::GraphQLTypeDefinitionFieldArg {
|
let field_type = util::GraphQLTypeDefinitionFieldArg {
|
||||||
|
|
|
@ -1701,7 +1701,10 @@ impl GraphQLTypeDefiniton {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let from_inputs = self.fields.iter().map(|field| {
|
let from_inputs = self
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.map(|field| {
|
||||||
let field_ident = &field.resolver_code;
|
let field_ident = &field.resolver_code;
|
||||||
let field_name = &field.name;
|
let field_name = &field.name;
|
||||||
|
|
||||||
|
@ -1721,14 +1724,12 @@ impl GraphQLTypeDefiniton {
|
||||||
match obj.get(#field_name) {
|
match obj.get(#field_name) {
|
||||||
#from_input_default
|
#from_input_default
|
||||||
Some(ref v) => ::juniper::FromInputValue::from_input_value(v).unwrap(),
|
Some(ref v) => ::juniper::FromInputValue::from_input_value(v).unwrap(),
|
||||||
None => {
|
None => ::juniper::FromInputValue::<#scalar>::from_implicit_null(),
|
||||||
::juniper::FromInputValue::from_input_value(&::juniper::InputValue::<#scalar>::null())
|
|
||||||
.unwrap()
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}).collect::<Vec<_>>();
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let to_inputs = self
|
let to_inputs = self
|
||||||
.fields
|
.fields
|
||||||
|
|
Loading…
Reference in a new issue