Skip to content

Commit

Permalink
[RFC] Null value (graphql#83)
Browse files Browse the repository at this point in the history
This proposal adds a null literal to the GraphQL language and allows it to be provided to nullable typed arguments and input object fields.

This presents an opportunity for a GraphQL service to interpret the explicitly provided null differently from the implicitly not-provided value, which may be especially useful when performing mutations using a per-field set API.

For example, this query may represent the removal/clearing of the "bar" field from thing with id: 4.

```
mutation editThing {
  editThing(id: 4, edits: { foo: "added", bar: null }) {
    # ...
  }
}
```

In addition to allowing `null` as a literal value, this also proposes interpretting the variables JSON to distinguish between explicit null and implicit not provided:

```
mutation editThing($edits: EditObj) {
  editThing(id: 4, edits: $edits) {
    # ...
  }
}
```

This variables results in the unsetting of `bar`

```
{ "edits": { "foo": "added", "bar": null } }
```

Finally, this proposes following the not-provided-ness of variables to their positions:

```
mutation editThing($editBaz: String) {
  editThing(id: 4, edits: { foo: "added", bar: null, baz: $editBaz }) {
    # ...
  }
}
```

Such that the three variables are semantically different:

 * `{}` The "baz" input field is "not provided"
 * `{"editBaz": null}` The "baz" input field is `null`
 * `{"editBaz": "added"}` The "baz" input field is `"added"`
  • Loading branch information
leebyron authored and IvanGoncharov committed Jun 17, 2017
1 parent ed7ab3e commit 514148e
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 34 deletions.
3 changes: 3 additions & 0 deletions spec/Appendix B -- Grammar Summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,15 @@ Value[Const] :
- FloatValue
- StringValue
- BooleanValue
- NullValue
- EnumValue
- ListValue[?Const]
- ObjectValue[?Const]

BooleanValue : one of `true` `false`

NullValue : `null`

EnumValue : Name but not `true`, `false` or `null`

ListValue[Const] :
Expand Down
35 changes: 32 additions & 3 deletions spec/Section 2 -- Language.md
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,7 @@ Value[Const] :
- FloatValue
- StringValue
- BooleanValue
- NullValue
- EnumValue
- ListValue[?Const]
- ObjectValue[?Const]
Expand Down Expand Up @@ -741,6 +742,37 @@ StringCharacter :: \ EscapedCharacter
* Return the character value of {EscapedCharacter}.


### Null Value

NullValue : `null`

Null values are represented as the keyword {null}.

GraphQL has two semantically different ways to represent the lack of a value:

* Explicitly providing the literal value: {null}.
* Implicitly not providing a value at all.

For example, these two field calls are similar, but are not identical:

```graphql
{
field(arg: null)
field
}
```

The first has explictly 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. Niether form may be used for an
input expecting a Non-Null type.

Note: The same two methods of representing the lack of a value are possible via
variables by either providing the a variable value as {null} and not providing
a variable value at all.


### Enum Value

EnumValue : Name but not `true`, `false` or `null`
Expand All @@ -750,9 +782,6 @@ recommended that Enum values be "all caps". Enum values are only used in
contexts where the precise enumeration type is known. Therefore it's not
necessary to supply an enumeration type name in the literal.

An enum value cannot be "null" in order to avoid confusion. GraphQL
does not supply a value literal to represent the concept {null}.


### List Value

Expand Down
65 changes: 41 additions & 24 deletions spec/Section 3 -- Type System.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ and floating-point values, they are interpreted as an integer input value if
they have an empty fractional part (ex. `1.0`) and otherwise as floating-point
input value.

For all types below, with the exception of Non-Null, if the explicit value
{null} is provided, then then the result of input coercion is {null}.

**Built-in Scalars**

GraphQL provides a basic set of well-defined Scalar types. A GraphQL server
Expand Down Expand Up @@ -809,16 +812,21 @@ input ExampleInputObject {

Original Value | Variables | Coerced Value
-------------------------------------------------------------------------------
`{ a: "abc", b: 123 }` | `{}` | `{ a: "abc", b: 123 }`
`{ a: 123, b: "123" }` | `{}` | `{ a: "123", b: 123 }`
`{ a: "abc" }` | `{}` | Error: Missing required field {b}
`{ a: "abc", b: 123 }` | | `{ a: "abc", b: 123 }`
`{ a: 123, b: "123" }` | | `{ a: "123", b: 123 }`
`{ a: "abc" }` | | Error: Missing required field {b}
`{ a: "abc", b: null }` | | Error: {b} must be non-null.
`{ a: null, b: 1 }` | | `{ a: null, b: 1 }`
`{ b: $var }` | `{ var: 123 }` | `{ b: 123 }`
`{ b: $var }` | `{}` | Error: Missing required field {b}.
`{ b: $var }` | `{ var: null }` | Error: {b} must be non-null.
`{ b: $var }` | `{}` | Error: {b} must be non-null.
`{ b: $var }` | `{}` | Error: {b} must be non-null.
`{ a: $var, b: 1 }` | `{ var: null }` | `{ a: null, b: 1 }`
`{ a: $var, b: 1 }` | `{}` | `{ b: 1 }`

Note: there is a semantic difference between the input value
explicitly declaring an input field's value as the value {null} vs having not
declared the input field at all.

#### Input Object type validation

1. An Input Object type must define one or more fields.
Expand Down Expand Up @@ -867,10 +875,21 @@ By default, all types in GraphQL are nullable; the {null} value is a valid
response for all of the above types. To declare a type that disallows null,
the GraphQL Non-Null type can be used. This type wraps an underlying type,
and this type acts identically to that wrapped type, with the exception
that `null` is not a valid response for the wrapping type. A trailing
that {null} is not a valid response for the wrapping type. A trailing
exclamation mark is used to denote a field that uses a Non-Null type like this:
`name: String!`.

**Nullable vs. Optional**

Fields are *always* optional within the context of a query, a field may be
omitted and the query is still valid. However fields that return Non-Null types
will never return the value {null} if queried.

Inputs (such as field arguments), are always optional by default. However a
non-null input type is required. In addition to not accepting the value {null},
it also does not accept omission. For the sake of simplicity nullable types
are always optional and non-null types are always required.

**Result Coercion**

In all of the above result coercions, {null} was considered a valid value.
Expand All @@ -881,43 +900,41 @@ must be raised.

**Input Coercion**

If the argument of a Non-Null type is not provided, a query error must
be raised.
If an argument or input-object field of a Non-Null type is not provided, is
provided with the literal value {null}, or is provided with a variable that was
either not provided a value at runtime, or was provided the value {null}, then
a query error must be raised.

If an argument of a Non-Null type is provided with a literal value, it is
coerced using the input coercion for the wrapped type.
If the value provided to the Non-Null type is provided with a literal value
other than {null}, or a Non-Null variable value, it is coerced using the input coercion for the wrapped type.

If the argument of a Non-Null is provided with a variable, a query error must be
raised if the runtime provided value is not provided or is {null} in the
provided representation (usually JSON). Otherwise, the coerced value is the
result of using the input coercion for the wrapped type.

Note that `null` is not a value in GraphQL, so a query cannot look like:
Example: A non-null argument cannot be omitted.

```!graphql
{
field(arg: null)
fieldWithNonNullArg
}
```

to indicate that the argument is {null}. Instead, an argument would be {null}
only if it is omitted:
Example: The value {null} cannot be provided to a non-null argument.

```graphql
```!graphql
{
field
fieldWithNonNullArg(nonNullArg: null)
}
```

Or if passed a variable of a nullable type that at runtime was not provided
a value:
Example: A variable of a nullable type cannot be provided to a non-null argument.

```graphql
query withNullableVariable($var: String) {
field(arg: $var)
fieldWithNonNullArg(nonNullArg: $var)
}
```

Note: The Validation section defines providing a nullable variable type to
a non-null input type as invalid.

**Non-Null type validation**

1. A Non-Null type must not wrap another Non-Null type.
Expand Down
23 changes: 17 additions & 6 deletions spec/Section 5 -- Validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -629,22 +629,26 @@ fragment stringIntoInt on Arguments {
```


#### Required Arguments
#### Required Non-Null Arguments

* For each Field or Directive in the document.
* Let {arguments} be the arguments provided by the Field or Directive.
* Let {argumentDefinitions} be the set of argument definitions of that Field or Directive.
* For each {definition} in {argumentDefinitions}
* Let {type} be the expected type of {definition}
* If {type} is Non-Null
* Let {argumentName} be the name of {definition}
* For each {definition} in {argumentDefinitions}:
* Let {type} be the expected type of {definition}.
* If {type} is Non-Null:
* Let {argumentName} be the name of {definition}.
* Let {argument} be the argument in {arguments} named {argumentName}
* {argument} must exist.
* Let {value} be the value of {argument}.
* {value} must not be the {null} literal.

** Explanatory Text **

Arguments can be required. Arguments are required if the type of the argument
is non-null. If it is not non-null, the argument is optional.
is non-null. If it is not non-null, the argument is optional. When an argument
type is non-null, and is required, the explicit value {null} may also not
be provided.

For example the following are valid:

Expand Down Expand Up @@ -676,6 +680,13 @@ fragment missingRequiredArg on Arguments {
}
```

Providing the explicit value {null} is also not valid.

```!graphql
fragment missingRequiredArg on Arguments {
notNullBooleanArgField(nonNullBooleanArg: null)
}
```

## Fragments

Expand Down
2 changes: 1 addition & 1 deletion spec/Section 6 -- Execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ the field returned {null}, and an error must be added to the {"errors"} list
in the response.

If the result of resolving a field is {null} (either because the function to
resolve the field returned `null` or because an error occurred), and that
resolve the field returned {null} or because an error occurred), and that
field is of a `Non-Null` type, then a field error is thrown. The
error must be added to the {"errors"} list in the response.

Expand Down

0 comments on commit 514148e

Please sign in to comment.