Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional v. Nullable input redux #872

Open
coady opened this issue May 22, 2021 · 4 comments
Open

Optional v. Nullable input redux #872

coady opened this issue May 22, 2021 · 4 comments

Comments

@coady
Copy link

coady commented May 22, 2021

An alternative proposal for the often requested (#476, #542) ability to distinguish optional inputs from nullable inputs. It doesn't propose any change to core GraphQL, just conventions and directives.

It can be summarized as:

  • Encourage using default values wherever possible, even a default of null.
  • Use standard directives as documentation. Extensions are free to implement validation based on the directives, but that's not part of the proposal.

Directives

"""
This input is optional, not nullable.
If the client insists on sending an explicit null value, the behavior is undefined.
"""
directive @optional on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION

"""
This input is nullable, not optional.
If the client insists on omitting the input value, the behavior is undefined.
"""
directive @required on ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION

Usage

Nullability Optionality Defaults Definition
Non-Null Required Type!
Non-Null Optional Has Default Type! = value
Non-Null Optional No Default Type @optional
Nullable Required Type @required
Nullable Optional Omitted == null Type = null
Nullable Optional Omitted != null Type

Descriptions

Type!

Works as always.

Type! = value

Already supported, just needs more documentation and encouragement. It may come as a surprise in this forum, but many developers don't know that an input can have a default, or that a default alone makes it optional.

  • The introductory example uses a nullable: length(unit: LengthUnit = METER): Float. Using LengthUnit! would make the example clearer.
  • The spec contains conflicting statements which have become conventional wisdom:

    For the sake of simplicity nullable types are always optional and non‐null types are always required.

Type @optional

The client knows that the service doesn't want an explicit null.

Type @required

The client knows that the service doesn't want an omitted input. Arguably this use case doesn't exist organically; it's for those who want to return an error on principle.

Type = null

Already supported; presumably it's not common because it appears pointless. But it has an important point in this context: guaranteeing to the client that omission and explicit null behave identically.

Type

Works as always, but with a different implication. Clients can infer that omission and explicit nulls have different semantics, otherwise one of the other options should have been chosen. A partial update mutation is the typical example, but this is common in queries as well. Consider filter predicates like (equal: String, contains: String, ...). null is likely a valid input to equal, and omission indicates to not filter. Whereas contains is likely being forced to be nullable, and could be annotated with @optional instead.

@benjie
Copy link
Member

benjie commented May 24, 2021

I like this "no change" approach - handling the issue with conventions; maybe you could write it up as a named specification in the same way that Relay wrote up their specifications? Then you can encourage the various GraphQL libraries/clients to add support for your specification.

Does it need any special handling for lists / lists-of-lists?

@coady
Copy link
Author

coady commented May 26, 2021

Does it need any special handling for lists / lists-of-lists?

No, maybe just more examples. Calling out that any value is a valid default value, including {} and []. And that it's common that an omitted list behaves identically to an empty list: [Type!]! = [].

@fluidsonic
Copy link
Contributor

I'm using @optional in my project. The main issue is supporting it for non-nullable types which for me is the most important use case. That requires breaking the spec but should not break backward-compatibility.

Based on #542 (comment):

type Mutation {
   updateContact(
      id: ID!,
      firstName: String! @optional,
      lastName: String! @optional,
      birthday: LocalDate @optional,
      phoneNumbers: [String!] @optional
   ): Contact!
}
  • id is always required.
  • firstName and lastName are optional (= may be updated) but if specified must be non-null.
  • birthday and phoneNumbers are optional (= may be updated) but can be specified null (or []) explicitly to unset.

That's still backward-compatible.

  • Servers that don't support @optional yet simply don't support new schemas that use @optional.
  • Clients that don't support @optional yet simply have to provide the current value for such fields, as is the case at the moment.

@peldax
Copy link

peldax commented Jun 8, 2021

This is great proposal and I am going to experiment little bit and implement it in my server implementation.

I see one possible problem, if this proposal (maybe sometime in the future) tried to be part of the official specs.

There is a standard that spec-declared directives appear in the Introspection (as displayed by the deprecated and specifiedBy directive) as fields. In Introspection both Arguments and InputFields are represented as __InputValue type, but your proposed directives make sense only for InputFields.

One possible solution would be declaring that isRequired and isOptional is resolving to null when __InputValue represents Argument.

EDIT: After re-reading specific sections in the specs, I realized that arguments can also be omitted. Please correct me if I am wrong. In this case I agree that specs should be more explicit and clear about default/omitted values.

coady added a commit to coady/graphique that referenced this issue Oct 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants