Skip to content

Commit

Permalink
[RFC] Null value
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 committed Aug 19, 2015
1 parent 7fe69f9 commit ba9b11d
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 33 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 @@ -129,12 +129,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
29 changes: 26 additions & 3 deletions spec/Section 2 -- Language.md
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ Value[Const] :
- FloatValue
- StringValue
- BooleanValue
- NullValue
- EnumValue
- ListValue[?Const]
- ObjectValue[?Const]
Expand Down Expand Up @@ -650,6 +651,31 @@ Strings are lists of characters wrapped in double-quotes `"`. (ex.
`"Hello World"`). White space and other otherwise-ignored characters are
significant within a string value.

#### Null Value

NullValue: `null`

Null values are represented as the keyword `null`.

GraphQL has two similar but not-identical ways to represent the lack of a value,
by explicitly providing the `null` value, or by implicitly not providing a value
at all.

For example, these two field calls are similar, but 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". A GraphQL service may
interpret the two forms differently, if it is advantageous. For example, to
represent deleting a field vs not altering a field during a
mutation, respectively.

#### Enum Value

EnumValue : Name but not `true`, `false` or `null`
Expand All @@ -659,9 +685,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

ListValue[Const] :
Expand Down
60 changes: 38 additions & 22 deletions spec/Section 3 -- Type System.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,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 @@ -649,14 +652,26 @@ An input object is never a valid result.
**Input Coercion**

The input to an input object should be an unordered map, otherwise an error
should be thrown. The result of the coercion is an unordered map, with an
entry for each input field, whose key is the name of the input field.
The value of an entry in the coerced map is the result of input coercing the
value of the entry in the input with the same key; if the input does not have a
corresponding entry, the value is the result of coercing null. The input
coercion above should be performed according to the input coercion rules of the
should be thrown. This unordered map should not contain any entries with names
not defined by a field of this input object type, otherwise an error should be
thrown.

The result of coercion is an environment-specific unordered map or record struct
defining slots for each field of the input object type.

For each field of the input object type, if the original value has an entry with
the same name, and the value at that entry is a literal value or a variable
which was provided a runtime value, an entry is added to the result with the
name of the field.

The value of that entry in the result is the outcome of input coercing the
original entry value according to the input coercion rules of the
type declared by the input field.

Note: there is a potential 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. GraphQL services may optionally interpret these
two cases differently if it is advantageous.

### Lists

Expand Down Expand Up @@ -706,40 +721,41 @@ then an error should be raised.

**Input Coercion**

Note that `null` is not a value in GraphQL, so a query cannot look like:
There are a number of ways in which a `null` value may be provided in GraphQL,
all of which must raise an error when provided to a Non-Null type.

If a value is not provided, or the explicit `null` value is provided, an error
must be thrown. Otherwise, the result is the outcome of input coercing the value
according to the input coercion rules of the wrapped type.

The explicit `null` value is not allowed given a non-null input type:

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

to indicate that the argument is null. Instead, an argument would be null only
if it is omitted:
An omitted argument is not allowed given a non-null input type:

```graphql
```!graphql
{
field
fieldWithNonNullArg
}
```

Or if passed a variable of a nullable type that at runtime was not provided
a value:
A variable of a nullable type that at runtime was not provided a value, or was
provided an explicit `null` value is not allowed given a non-null input type.

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

Hence, if the value for a Non Null type is hard-coded in the query, it is always
coerced using the input coercion for the wrapped type.
Note: the Validation chapter also defines providing a nullable variable type to
a non-null input type as invalid.

When a Non Null input has its value set using a variable, the coerced value
should be `null` if the provided value is `null`-like in the provided
representation, or if the provided value is omitted. Otherwise, the coerced
value is the result of running the wrapped type's input coercion on the
provided value.

## Directives

Expand Down
14 changes: 13 additions & 1 deletion spec/Section 5 -- Validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -518,11 +518,15 @@ fragment stringIntoInt on Arguments {
* 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 @@ -554,6 +558,14 @@ fragment missingRequiredArg on Arguments {
}
```

Providing the explicit value `null` is also not valid.

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

## Fragments

### Fragment Declarations
Expand Down
47 changes: 40 additions & 7 deletions spec/Section 6 -- Execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,20 +137,23 @@ GetFieldEntry(objectType, object, fields):
{GetFieldTypeFromObjectType(objectType, firstField)}.
* If {fieldType} is {null}, return {null}, indicating that no entry exists in
the result map.
* Let {resolvedObject} be {ResolveFieldOnObject(objectType, object, fieldEntry)}.
* Let {resolvedObject} be {ResolveFieldOnObject(objectType, object, firstField)}.
* If {resolvedObject} is {null}, return {tuple(responseKey, null)},
indicating that an entry exists in the result map whose value is `null`.
* Let {subSelectionSet} be the result of calling {MergeSelectionSets(fields)}.
* Let {responseValue} be the result of calling {CompleteValue(fieldType, resolvedObject, subSelectionSet)}.
* Return {tuple(responseKey, responseValue)}.

GetFieldTypeFromObjectType(objectType, firstField):
* Call the method provided by the type system for determining the field type
on a given object type.
GetFieldTypeFromObjectType(objectType, field):
* Let {fieldName} be the name of {field}.
* Return the field type defined by {objectType} with the name {fieldName}.

ResolveFieldOnObject(objectType, object, firstField):
* Call the method provided by the type system for determining the resolution
of a field on a given object.
ResolveFieldOnObject(objectType, object, field):
* Let {fieldName} be the name of {field}.
* Let {arguments} be the result of {ResolveArguments(objectType, field)}
* Call the internal function provided by {objectType} for determining the
resolved value of a field named {fieldName} on a given {object}
provided {arguments}.

MergeSelectionSets(fields):
* Let {selectionSet} be an empty list.
Expand Down Expand Up @@ -189,6 +192,36 @@ ResolveAbstractType(abstractType, objectValue):
system for determining the Object type of {abstractType} given the
value {objectValue}.

### Field Arguments

Fields may include arguments which are provided to the underlying runtime in
order to correctly produce a value. These arguments are defined by the field in
the type system to have a specific input type: Scalars, Enum, Input Object, or
List or Non-Null wrapped of these three.

At each argument position in a query may be a literal value or a variable to be
provided at runtime.

ResolveArguments(objectType, field)
* Let {fieldName} be the name of {field}.
* Let {argTypes} be the arguments provided by {objectType} for the field
named {fieldName}.
* Let {argValues} be an empty Map.
* For each {argTypes} as {argName} and {argType}:
* Let {arg} be the argument in {field} named {argName}.
* If {arg} exists:
* Let {value} be the value defined by {arg}.
* If {value} is a Variable:
* If a value was provided for the variable {value}:
* Add an entry to {argValues} named {argName} with the runtime value
of the Variable {value}.
* Else:
* Let {coercedValue} be the result of coercing {value} according to the
input coercion rules of {argType}.
* Add an entry to {argValues} named {argName} with the
value {coercedValue}.
* Return {argValues}.

### Normal evaluation

When evaluating a grouped field set without a serial execution order requirement,
Expand Down

0 comments on commit ba9b11d

Please sign in to comment.