diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 3ac7c7e60..f5e245ab3 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -1239,6 +1239,11 @@ NonNullType : - NamedType ! - ListType ! +SemanticNonNullType : + +- ! 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. diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index d32b08566..5b7248d2b 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -953,16 +953,23 @@ IsValidImplementationFieldType(fieldType, implementedFieldType): 1. If {fieldType} is a Non-Null type: 1. Let {nullableType} be the unwrapped nullable type of {fieldType}. 2. Let {implementedNullableType} be the unwrapped nullable type of - {implementedFieldType} if it is a Non-Null type, otherwise let it be - {implementedFieldType} directly. + {implementedFieldType} if it is a Non-Null type or Semantic-Non-Null type, + otherwise let it be {implementedFieldType} directly. 3. Return {IsValidImplementationFieldType(nullableType, implementedNullableType)}. -2. If {fieldType} is a List type and {implementedFieldType} is also a List type: +2. If {fieldType} is a Semantic-Non-Null type: + 1. Let {nullableType} be the unwrapped nullable type of {fieldType}. + 2. Let {implementedNullableType} be the unwrapped nullable type of + {implementedFieldType} if it is a Semantic-Non-Null type, otherwise let it + be {implementedFieldType} directly. + 3. Return {IsValidImplementationFieldType(nullableType, + implementedNullableType)}. +3. If {fieldType} is a List type and {implementedFieldType} is also a List type: 1. Let {itemType} be the unwrapped item type of {fieldType}. 2. Let {implementedItemType} be the unwrapped item type of {implementedFieldType}. 3. Return {IsValidImplementationFieldType(itemType, implementedItemType)}. -3. Return {IsSubType(fieldType, implementedFieldType)}. +4. Return {IsSubType(fieldType, implementedFieldType)}. IsSubType(possibleSubType, superType): @@ -1859,6 +1866,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 Semantic-Non-Null type. ### Combining List and Non-Null @@ -1892,6 +1900,83 @@ 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 | +## Semantic-Non-Null + +The GraphQL Semantic-Non-Null 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 leading +exclamation mark is used to denote a field that uses a Semantic-Non-Null type +like this: `name: !String`. + +Semantic-Non-Null 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 Semantic-Non-Null 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 Semantic-Non-Null type, the coercion of the wrapped +type should be performed. If that result was not {null}, then the result of +coercing the Semantic-Non-Null 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 Semantic-Non-Null 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** + +Semantic-Non-Null types are never valid inputs. + +**Type Validation** + +1. A Semantic-Non-Null type must wrap an _output type_. +1. A Semantic-Non-Null type must not wrap another Semantic-Non-Null type. +1. A Semantic-Non-Null type must not wrap a Non-Null type. + +### Combining List and Semantic-Non-Null + +The List and Semantic-Non-Null wrapping types can compose, representing more +complex types. The rules for result coercion of Lists and Semantic-Non-Null +types apply in a recursive fashion. + +For example if the inner item type of a List is Semantic-Non-Null (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 Semantic-Non-Null 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` (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` (With logged coercion error) | +| `![Int!]` | `[1, 2, null]` | `null` (With logged coercion error) | +| `![Int!]` | `[1, 2, Error]` | `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` | Error: Value cannot be 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? diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 3054a9f6c..6d150a52d 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -162,13 +162,14 @@ enum __TypeKind { INPUT_OBJECT LIST NON_NULL + SEMANTIC_NON_NULL } type __Field { name: String! description: String args(includeDeprecated: Boolean = false): [__InputValue!]! - type: __Type! + type(includeSemanticNonNull: Boolean! = false): __Type! isDeprecated: Boolean! deprecationReason: String } @@ -263,6 +264,7 @@ possible value of the `__TypeKind` enum: - {"INPUT_OBJECT"} - {"LIST"} - {"NON_NULL"} +- {"SEMANTIC_NON_NULL"} **Scalar** @@ -400,12 +402,33 @@ 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 Semantic-Non-Null 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 Semantic-Non-Null. +- All other fields must return {null}. + +**Semantic-Non-Null** + +GraphQL types are nullable. The value {null} is a valid response for field type. + +A Semantic-Non-Null type is a type modifier: it wraps another _output type_ +instance in the `ofType` field. Semantic-Non-Null 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 Semantic-Non-Null of Lists. However it must not +be a modified Semantic-Non-Null type to avoid a redundant Null-Only-On-Error of +Semantic-Non-Null; nor may it be a modified Non-Null type since these types are +mutually exclusive. + +Fields\: + +- `kind` must return `__TypeKind.SEMANTIC_NON_NULL`. +- `ofType` must return a type of any kind except Non-Null and Semantic-Non-Null. - All other fields must return {null}. ### The \_\_Field Type @@ -422,10 +445,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 `includeSemanticNonNull` which defaults to {false}. If + {false}, let {fieldType} be the type of value returned by this field and + instead return a `__Type` that represents + {RecursivelyStripSemanticNonNullTypes(fieldType)}. - `isDeprecated` returns {true} if this field should no longer be used, otherwise {false}. - `deprecationReason` optionally provides a reason why this field is deprecated. +RecursivelyStripSemanticNonNullTypes(type): + +- If {type} is a Semantic-Non-Null type: + - Let {innerType} be the inner type of {type}. + - Return {RecursivelyStripSemanticNonNullTypes(innerType)}. +- Otherwise, return {type}. + +Note: This algorithm recursively removes all Semantic-Non-Null type wrappers +(e.g. `![[!Int]!]` would become `[[Int]!]`). This is to support legacy clients: +they can safely treat a Semantic-Non-Null type as the underlying nullable type. + ### The \_\_InputValue Type The `__InputValue` type represents field and directive arguments as well as the diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index f357069f9..4e7bd0571 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -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 Semantic-Non-Null type: - Let {innerType} be the inner type of {fieldType}. - Let {completedResult} be the result of calling {CompleteValue(innerType, fields, result, variableValues)}. @@ -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 `Semantic-Non-Null` types do not +propagate.