Skip to content

Commit f0e3d6e

Browse files
committed
Add specification changes for Null-Only-On-Error type
1 parent 3adfcca commit f0e3d6e

4 files changed

+126
-4
lines changed

spec/Section 2 -- Language.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,11 @@ NonNullType :
12391239
- NamedType !
12401240
- ListType !
12411241

1242+
NullOnlyOnErrorType :
1243+
1244+
- NamedType \*
1245+
- ListType \*
1246+
12421247
GraphQL describes the types of data expected by arguments and variables. Input
12431248
types may be lists of another input type, or a non-null variant of any other
12441249
input type.

spec/Section 3 -- Type System.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1859,6 +1859,7 @@ non-null input type as invalid.
18591859
**Type Validation**
18601860

18611861
1. A Non-Null type must not wrap another Non-Null type.
1862+
1. A Non-Null type must not wrap a Null-Only-On-Error type.
18621863

18631864
### Combining List and Non-Null
18641865

@@ -1892,6 +1893,79 @@ Following are examples of result coercion with various types and values:
18921893
| `[Int!]!` | `[1, 2, null]` | Error: Item cannot be null |
18931894
| `[Int!]!` | `[1, 2, Error]` | Error: Error occurred in item |
18941895

1896+
## Null-Only-On-Error
1897+
1898+
The GraphQL Null-Only-On-Error type is an alternative to the GraphQL Non-Null
1899+
type to disallow null unless accompanied by a field error. This type wraps an
1900+
underlying type, and this type acts identically to that wrapped type, with the
1901+
exception that {null} will result in a field error being raised. A trailing
1902+
asterisk is used to denote a field that uses a Null-Only-On-Error type like
1903+
this: `name: String*`.
1904+
1905+
Null-Only-On-Error types are only valid for use as an _output type_; they must
1906+
not be used as an _input type_.
1907+
1908+
**Nullable vs. Optional**
1909+
1910+
Fields that return Null-Only-On-Error types will never return the value {null}
1911+
if queried _unless_ an error has been logged for that field.
1912+
1913+
**Result Coercion**
1914+
1915+
To coerce the result of a Null-Only-On-Error type, the coercion of the wrapped
1916+
type should be performed. If that result was not {null}, then the result of
1917+
coercing the Null-Only-On-Error type is that result. If that result was {null},
1918+
then a _field error_ must be raised.
1919+
1920+
Note: When a _field error_ is raised on a Null-Only-On-Error value, the error
1921+
does not propagate to the parent field, instead {null} is used for the value.
1922+
For more information on this process, see
1923+
[Handling Field Errors](#sec-Handling-Field-Errors) within the Execution
1924+
section.
1925+
1926+
**Input Coercion**
1927+
1928+
Null-Only-On-Error types are never valid inputs.
1929+
1930+
**Type Validation**
1931+
1932+
1. A Null-Only-On-Error type must wrap an _output type_.
1933+
1. A Null-Only-On-Error type must not wrap another Null-Only-On-Error type.
1934+
1. A Null-Only-On-Error type must not wrap a Non-Null type.
1935+
1936+
### Combining List and Null-Only-On-Error
1937+
1938+
The List and Null-Only-On-Error wrapping types can compose, representing more
1939+
complex types. The rules for result coercion of Lists and Null-Only-On-Error
1940+
types apply in a recursive fashion.
1941+
1942+
For example if the inner item type of a List is Null-Only-On-Error (e.g.
1943+
`[T*]`), then that List may not contain any {null} items unless associated field
1944+
errors were raised. However if the inner type of a Null-Only-On-Error is a List
1945+
(e.g. `[T]*`), then {null} is not accepted without an accompanying field error
1946+
being raised, however an empty list is accepted.
1947+
1948+
Following are examples of result coercion with various types and values:
1949+
1950+
| Expected Type | Internal Value | Coerced Result |
1951+
| ------------- | --------------- | ------------------------------------------- |
1952+
| `[Int]` | `[1, 2, 3]` | `[1, 2, 3]` |
1953+
| `[Int]` | `null` | `null` |
1954+
| `[Int]` | `[1, 2, null]` | `[1, 2, null]` |
1955+
| `[Int]` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) |
1956+
| `[Int]*` | `[1, 2, 3]` | `[1, 2, 3]` |
1957+
| `[Int]*` | `null` | `null` (With logged coercion error) |
1958+
| `[Int]*` | `[1, 2, null]` | `[1, 2, null]` |
1959+
| `[Int]*` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) |
1960+
| `[Int*]` | `[1, 2, 3]` | `[1, 2, 3]` |
1961+
| `[Int*]` | `null` | `null` |
1962+
| `[Int*]` | `[1, 2, null]` | `[1, 2, null]` (With logged coercion error) |
1963+
| `[Int*]` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) |
1964+
| `[Int*]*` | `[1, 2, 3]` | `[1, 2, 3]` |
1965+
| `[Int*]*` | `null` | `null` (With logged coercion error) |
1966+
| `[Int*]*` | `[1, 2, null]` | `[1, 2, null]` (With logged coercion error) |
1967+
| `[Int*]*` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) |
1968+
18951969
## Directives
18961970

18971971
DirectiveDefinition : Description? directive @ Name ArgumentsDefinition?

spec/Section 4 -- Introspection.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,14 @@ enum __TypeKind {
162162
INPUT_OBJECT
163163
LIST
164164
NON_NULL
165+
NULL_ONLY_ON_ERROR
165166
}
166167

167168
type __Field {
168169
name: String!
169170
description: String
170171
args(includeDeprecated: Boolean = false): [__InputValue!]!
171-
type: __Type!
172+
type(includeNullOnlyOnError: Boolean! = false): __Type!
172173
isDeprecated: Boolean!
173174
deprecationReason: String
174175
}
@@ -263,6 +264,7 @@ possible value of the `__TypeKind` enum:
263264
- {"INPUT_OBJECT"}
264265
- {"LIST"}
265266
- {"NON_NULL"}
267+
- {"NULL_ONLY_ON_ERROR"}
266268

267269
**Scalar**
268270

@@ -400,12 +402,35 @@ required inputs for arguments and input object fields.
400402

401403
The modified type in the `ofType` field may itself be a modified List type,
402404
allowing the representation of Non-Null of Lists. However it must not be a
403-
modified Non-Null type to avoid a redundant Non-Null of Non-Null.
405+
modified Non-Null type to avoid a redundant Non-Null of Non-Null; nor may it be
406+
a modified Null-Only-On-Error type since these types are mutually exclusive.
404407

405408
Fields\:
406409

407410
- `kind` must return `__TypeKind.NON_NULL`.
408-
- `ofType` must return a type of any kind except Non-Null.
411+
- `ofType` must return a type of any kind except Non-Null and
412+
Null-Only-On-Error.
413+
- All other fields must return {null}.
414+
415+
**Null-Only-On-Error**
416+
417+
GraphQL types are nullable. The value {null} is a valid response for field type.
418+
419+
A Null-Only-On-Error type is a type modifier: it wraps another _output type_
420+
instance in the `ofType` field. Null-Only-On-Error types do not allow {null} as
421+
a response _unless_ an associated _field error_ has been raised.
422+
423+
The modified type in the `ofType` field may itself be a modified List type,
424+
allowing the representation of Null-Only-On-Error of Lists. However it must not
425+
be a modified Null-Only-On-Error type to avoid a redundant Null-Only-On-Error of
426+
Null-Only-On-Error; nor may it be a modified Non-Null type since these types are
427+
mutually exclusive.
428+
429+
Fields\:
430+
431+
- `kind` must return `__TypeKind.NULL_ONLY_ON_ERROR`.
432+
- `ofType` must return a type of any kind except Non-Null and
433+
Null-Only-On-Error.
409434
- All other fields must return {null}.
410435

411436
### The \_\_Field Type
@@ -422,10 +447,25 @@ Fields\:
422447
{true}, deprecated arguments are also returned.
423448
- `type` must return a `__Type` that represents the type of value returned by
424449
this field.
450+
- Accepts the argument `includeNullOnlyOnError` which defaults to {false}. If
451+
{false}, let {fieldType} be the type of value returned by this field and
452+
instead return a `__Type` that represents
453+
{RecursivelyStripNullOnlyOnErrorTypes(fieldType)}.
425454
- `isDeprecated` returns {true} if this field should no longer be used,
426455
otherwise {false}.
427456
- `deprecationReason` optionally provides a reason why this field is deprecated.
428457

458+
RecursivelyStripNullOnlyOnErrorTypes(type):
459+
460+
- If {type} is a Null-Only-On-Error type:
461+
- Let {innerType} be the inner type of {type}.
462+
- Return {RecursivelyStripNullOnlyOnErrorTypes(innerType)}.
463+
- Otherwise, return {type}.
464+
465+
Note: This algorithm recursively removes all Null-Only-On-Error type wrappers
466+
(e.g. `[[Int*]!]*` would become `[[Int]!]`). This is to support legacy clients:
467+
they can safely treat a Null-Only-On-Error type as the underlying nullable type.
468+
429469
### The \_\_InputValue Type
430470

431471
The `__InputValue` type represents field and directive arguments as well as the

spec/Section 6 -- Execution.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ field execution process continues recursively.
670670

671671
CompleteValue(fieldType, fields, result, variableValues):
672672

673-
- If the {fieldType} is a Non-Null type:
673+
- If the {fieldType} is a Non-Null or a Null-Only-On-Error type:
674674
- Let {innerType} be the inner type of {fieldType}.
675675
- Let {completedResult} be the result of calling {CompleteValue(innerType,
676676
fields, result, variableValues)}.
@@ -805,3 +805,6 @@ upwards.
805805
If all fields from the root of the request to the source of the field error
806806
return `Non-Null` types, then the {"data"} entry in the response should be
807807
{null}.
808+
809+
Note: By the above, field errors that happen in `Null-Only-On-Error` types do
810+
not propagate.

0 commit comments

Comments
 (0)