juniper/docs/book/content/advanced/implicit_and_explicit_null.md
Kai Ren a3fda7363d
Rework codegen for GraphQL objects and subscriptions (#971, #421)
- preserve and reuse defined impl blocks in #[graphql_object] and #[graphql_subscription] macros expansion
- allow renaming `ScalarValue` type parameter in expanded code via `scalar = S: ScalarValue` syntax

Additionally:
- rename `rename` attribute's argument to `rename_all`
- support `rename_all` in #[graphql_interface] macro
2021-08-11 17:41:49 +03:00

3 KiB

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:

{
    field(arg: null)
}

Or they can simply omit the argument:

{
    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:

/// 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:

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:

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:

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:

# 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,
}
impl juniper::Context for Context {}

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.