Skip to content

Commit

Permalink
Add specification changes for Null-Only-On-Error type
Browse files Browse the repository at this point in the history
  • Loading branch information
benjie committed Oct 4, 2023
1 parent 3adfcca commit f0e3d6e
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 4 deletions.
5 changes: 5 additions & 0 deletions spec/Section 2 -- Language.md
Original file line number Diff line number Diff line change
Expand Up @@ -1239,6 +1239,11 @@ NonNullType :
- NamedType !
- ListType !

NullOnlyOnErrorType :

- NamedType \*
- ListType \*

GraphQL describes the types of data expected by arguments and variables. Input
types may be lists of another input type, or a non-null variant of any other
input type.
Expand Down
74 changes: 74 additions & 0 deletions spec/Section 3 -- Type System.md
Original file line number Diff line number Diff line change
Expand Up @@ -1859,6 +1859,7 @@ non-null input type as invalid.
**Type Validation**

1. A Non-Null type must not wrap another Non-Null type.
1. A Non-Null type must not wrap a Null-Only-On-Error type.

### Combining List and Non-Null

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

## Null-Only-On-Error

The GraphQL Null-Only-On-Error type is an alternative to the GraphQL Non-Null
type to disallow null unless accompanied by a field error. This type wraps an
underlying type, and this type acts identically to that wrapped type, with the
exception that {null} will result in a field error being raised. A trailing
asterisk is used to denote a field that uses a Null-Only-On-Error type like
this: `name: String*`.

Null-Only-On-Error types are only valid for use as an _output type_; they must
not be used as an _input type_.

**Nullable vs. Optional**

Fields that return Null-Only-On-Error types will never return the value {null}
if queried _unless_ an error has been logged for that field.

**Result Coercion**

To coerce the result of a Null-Only-On-Error type, the coercion of the wrapped
type should be performed. If that result was not {null}, then the result of
coercing the Null-Only-On-Error type is that result. If that result was {null},
then a _field error_ must be raised.

Note: When a _field error_ is raised on a Null-Only-On-Error value, the error
does not propagate to the parent field, instead {null} is used for the value.
For more information on this process, see
[Handling Field Errors](#sec-Handling-Field-Errors) within the Execution
section.

**Input Coercion**

Null-Only-On-Error types are never valid inputs.

**Type Validation**

1. A Null-Only-On-Error type must wrap an _output type_.
1. A Null-Only-On-Error type must not wrap another Null-Only-On-Error type.
1. A Null-Only-On-Error type must not wrap a Non-Null type.

### Combining List and Null-Only-On-Error

The List and Null-Only-On-Error wrapping types can compose, representing more
complex types. The rules for result coercion of Lists and Null-Only-On-Error
types apply in a recursive fashion.

For example if the inner item type of a List is Null-Only-On-Error (e.g.
`[T*]`), then that List may not contain any {null} items unless associated field
errors were raised. However if the inner type of a Null-Only-On-Error is a List
(e.g. `[T]*`), then {null} is not accepted without an accompanying field error
being raised, however an empty list is accepted.

Following are examples of result coercion with various types and values:

| Expected Type | Internal Value | Coerced Result |
| ------------- | --------------- | ------------------------------------------- |
| `[Int]` | `[1, 2, 3]` | `[1, 2, 3]` |
| `[Int]` | `null` | `null` |
| `[Int]` | `[1, 2, null]` | `[1, 2, null]` |
| `[Int]` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) |
| `[Int]*` | `[1, 2, 3]` | `[1, 2, 3]` |
| `[Int]*` | `null` | `null` (With logged coercion error) |
| `[Int]*` | `[1, 2, null]` | `[1, 2, null]` |
| `[Int]*` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) |
| `[Int*]` | `[1, 2, 3]` | `[1, 2, 3]` |
| `[Int*]` | `null` | `null` |
| `[Int*]` | `[1, 2, null]` | `[1, 2, null]` (With logged coercion error) |
| `[Int*]` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) |
| `[Int*]*` | `[1, 2, 3]` | `[1, 2, 3]` |
| `[Int*]*` | `null` | `null` (With logged coercion error) |
| `[Int*]*` | `[1, 2, null]` | `[1, 2, null]` (With logged coercion error) |
| `[Int*]*` | `[1, 2, Error]` | `[1, 2, null]` (With logged error) |

## Directives

DirectiveDefinition : Description? directive @ Name ArgumentsDefinition?
Expand Down
46 changes: 43 additions & 3 deletions spec/Section 4 -- Introspection.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,14 @@ enum __TypeKind {
INPUT_OBJECT
LIST
NON_NULL
NULL_ONLY_ON_ERROR
}

type __Field {
name: String!
description: String
args(includeDeprecated: Boolean = false): [__InputValue!]!
type: __Type!
type(includeNullOnlyOnError: Boolean! = false): __Type!
isDeprecated: Boolean!
deprecationReason: String
}
Expand Down Expand Up @@ -263,6 +264,7 @@ possible value of the `__TypeKind` enum:
- {"INPUT_OBJECT"}
- {"LIST"}
- {"NON_NULL"}
- {"NULL_ONLY_ON_ERROR"}

**Scalar**

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

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

Fields\:

- `kind` must return `__TypeKind.NON_NULL`.
- `ofType` must return a type of any kind except Non-Null.
- `ofType` must return a type of any kind except Non-Null and
Null-Only-On-Error.
- All other fields must return {null}.

**Null-Only-On-Error**

GraphQL types are nullable. The value {null} is a valid response for field type.

A Null-Only-On-Error type is a type modifier: it wraps another _output type_
instance in the `ofType` field. Null-Only-On-Error types do not allow {null} as
a response _unless_ an associated _field error_ has been raised.

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

Fields\:

- `kind` must return `__TypeKind.NULL_ONLY_ON_ERROR`.
- `ofType` must return a type of any kind except Non-Null and
Null-Only-On-Error.
- All other fields must return {null}.

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

RecursivelyStripNullOnlyOnErrorTypes(type):

- If {type} is a Null-Only-On-Error type:
- Let {innerType} be the inner type of {type}.
- Return {RecursivelyStripNullOnlyOnErrorTypes(innerType)}.
- Otherwise, return {type}.

Note: This algorithm recursively removes all Null-Only-On-Error type wrappers
(e.g. `[[Int*]!]*` would become `[[Int]!]`). This is to support legacy clients:
they can safely treat a Null-Only-On-Error type as the underlying nullable type.

### The \_\_InputValue Type

The `__InputValue` type represents field and directive arguments as well as the
Expand Down
5 changes: 4 additions & 1 deletion spec/Section 6 -- Execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ field execution process continues recursively.

CompleteValue(fieldType, fields, result, variableValues):

- If the {fieldType} is a Non-Null type:
- If the {fieldType} is a Non-Null or a Null-Only-On-Error type:
- Let {innerType} be the inner type of {fieldType}.
- Let {completedResult} be the result of calling {CompleteValue(innerType,
fields, result, variableValues)}.
Expand Down Expand Up @@ -805,3 +805,6 @@ upwards.
If all fields from the root of the request to the source of the field error
return `Non-Null` types, then the {"data"} entry in the response should be
{null}.

Note: By the above, field errors that happen in `Null-Only-On-Error` types do
not propagate.

0 comments on commit f0e3d6e

Please sign in to comment.