From 1666d2e778be581ba171152a1baad6c4bfc7c5f3 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 30 Apr 2025 16:32:36 +0100 Subject: [PATCH 1/5] Add Transitional Non-Null to the appendix --- spec/Appendix C -- Transitional Non-Null.md | 116 ++++++++++++++++++++ spec/GraphQL.md | 2 + 2 files changed, 118 insertions(+) create mode 100644 spec/Appendix C -- Transitional Non-Null.md diff --git a/spec/Appendix C -- Transitional Non-Null.md b/spec/Appendix C -- Transitional Non-Null.md new file mode 100644 index 000000000..b0f593ea7 --- /dev/null +++ b/spec/Appendix C -- Transitional Non-Null.md @@ -0,0 +1,116 @@ +# C. Appendix: Transitional Non-Null + +_This appendix defines an optional mechanism for marking fields as +"transitionally non-null" to allow schema evolution without breaking error +behavior for legacy clients. Implementations are not required to support this +feature, but doing so enables gradual migration toward semantic nullability +while preserving compatibility._ + +The introduction of _error behavior_ to this specification allows clients to +take responsibility for error handling, no longer having the schema perform +error propagation and destroying potentially useful response data in the +process. With this move towards clients handling errors, designers of new +schemas (or new fields in existing schemas) no longer need to factor whether or +not a field is likely to error into its nullability; designers can mark a +_semantically_ non-nullable _response position_ (a place where {null} is not a +semantically valid value for the data) as `Non-Null`, writing a {null} there on +error in the knowledge that the client now takes responsibility for handling +errors and preventing these placeholder {null} values from being read. + +However, for schema designers that need to support legacy clients that do not +exhibit these error handling properties, marking semantically non-nullable +response positions as `Non-Null` would mean that more of the response would be +destroyed for these clients on error, potentially turning local widget errors +into full screen errors. + +To allow you to add `Non-Null` to existing fields during this transitional time, +whilst the fields are still in use by legacy clients, without changing their +error propagation boundaries, this appendix introduces the optional +`@noPropagate` directive. + +## @noPropagate + +```graphql +directive @noPropagate(levels: [Int!]! = [0]) on FIELD_DEFINITION +``` + +The `@noPropagate` directive instructs the system to mark the non-null types at +the given levels in the field's return type as "transitional" non-null types +(see [Transitional Non-Null Type](#sec-Transitional-Non-Null-Type)). + +The `levels` argument identifies levels within the return type by counting each +list wrapper. Level 0 refers to the base type; each nested list increases the +level by 1 for its inner type. For the avoidance of doubt: `Non-Null` wrappers +do not increase the count. + +If a listed level corresponds to a nullable type in the return type, it has no +effect. + +For a field that does not return a list type you do not need to specify levels. +If a field returns a list type and you wish to mark the inner type as +`@noPropagate` only then you would provide `@noPropagate(levels: [1])`. + +Example: + +```graphql example +type Query { + myString: String! @noPropagate + # Equivalent to the above + myString2: String! @noPropagate(levels: [0]) + myList: [Int!]! @noPropagate(levels: [1]) +} +``` + +## Transitional Non-Null + +A "transitional" Non-Null type is a variant of a [Non-Null](#sec-Non-Null) type +that behaves identically to Non-Null with two exceptions: + +1. If an _execution error_ occurs in this response position, the error does not + propagate to the parent _response position_, instead the response position is + set to {null}. +2. When the _error behavior_ of the request is {"PROPAGATE"}, this _response + position_ must be exposed as nullable in introspection. + +### Changes: Handling Execution Errors + +When interpreting the +[Handling Execution Errors](#sec-Handling-Execution-Errors) and +[Errors and Non-Null Types](#sec-Executing-Selection-Sets.Errors-and-Non-Null-Types) +sections of the specification, Transitional Non-Null types should be treated as +if they were nullable types. This does not apply to {CompleteValue()} which +should still raise an _execution error_ if {null} is returned for a Transitional +Non-Null type. + +### Changes: Introspection + +Note: Transitional Non-Null types do not appear in the type system as a distinct +\_\_TypeKind. They are unwrapped to nullable types in introspection when the +error behavior is {"PROPAGATE"}, and appear as {"NON_NULL"} otherwise. + +**\_\_Field.type** + +When the request _error behavior_ is {"PROPAGATE"}, the `type` field on the +`__Field` introspection type must return a `__Type` that represents the type of +value returned by this field with the transitional Non-Null wrapper types +unwrapped at every level. + +**\_\_Field.noPropagateLevels** + +This additional field should be added to introspection: + +```graphql +extend type __Field { + noPropagateLevels: [Int!] +} +``` + +The list must match the `levels` that would be passed to `@noPropagate` to +describe the field’s transitional Non-Null wrappers, or `null` if no +`@noPropagate` would be needed. It must not be an empty list. + +### Changes: Type System + +When representing a GraphQL schema using the type system definition language, +any field whose return type involves Transitional Non-Null types must indicate +this via the `@noPropagate` directive. diff --git a/spec/GraphQL.md b/spec/GraphQL.md index fad6bcdbe..7abdf7078 100644 --- a/spec/GraphQL.md +++ b/spec/GraphQL.md @@ -139,3 +139,5 @@ Note: This is an example of a non-normative note. # [Appendix: Notation Conventions](Appendix%20A%20--%20Notation%20Conventions.md) # [Appendix: Grammar Summary](Appendix%20B%20--%20Grammar%20Summary.md) + +# [Appendix: Transitional Non-Null](Appendix%20C%20--%20Transitional%20Non-Null.md) From 26872eaab8d64cb17e67bd5158bfa6ea25ce14e0 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 30 Apr 2025 16:40:27 +0100 Subject: [PATCH 2/5] Tweaks --- spec/Appendix C -- Transitional Non-Null.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/spec/Appendix C -- Transitional Non-Null.md b/spec/Appendix C -- Transitional Non-Null.md index b0f593ea7..93461a4ee 100644 --- a/spec/Appendix C -- Transitional Non-Null.md +++ b/spec/Appendix C -- Transitional Non-Null.md @@ -1,5 +1,7 @@ # C. Appendix: Transitional Non-Null +## Overview + _This appendix defines an optional mechanism for marking fields as "transitionally non-null" to allow schema evolution without breaking error behavior for legacy clients. Implementations are not required to support this @@ -28,7 +30,7 @@ whilst the fields are still in use by legacy clients, without changing their error propagation boundaries, this appendix introduces the optional `@noPropagate` directive. -## @noPropagate +## The @noPropagate Directive ```graphql directive @noPropagate(levels: [Int!]! = [0]) on FIELD_DEFINITION @@ -50,7 +52,22 @@ For a field that does not return a list type you do not need to specify levels. If a field returns a list type and you wish to mark the inner type as `@noPropagate` only then you would provide `@noPropagate(levels: [1])`. -Example: +This example outlines how you might introduce semantic nullability into existing +fields in your schema, to reduce the number of null checks your error-handling +clients need to perform. Remember: new fields should reflect the semantic +nullability immediately, they do not need the `@noPropagate` directive since +there is no legacy to support. + +```diff example + type Query { +- myString: String ++ myString: String! @noPropagate +- myString2: String ++ myString2: String! @noPropagate(levels: [0]) +- myList: [Int]! ++ myList: [Int!]! @noPropagate(levels: [1]) + } +``` ```graphql example type Query { From 3c8a3c14102be346d590191922c34f4d683383da Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 30 Apr 2025 16:46:08 +0100 Subject: [PATCH 3/5] Revise first paragraph --- spec/Appendix C -- Transitional Non-Null.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/spec/Appendix C -- Transitional Non-Null.md b/spec/Appendix C -- Transitional Non-Null.md index 93461a4ee..497550359 100644 --- a/spec/Appendix C -- Transitional Non-Null.md +++ b/spec/Appendix C -- Transitional Non-Null.md @@ -1,12 +1,13 @@ # C. Appendix: Transitional Non-Null +Note: This appendix defines an optional mechanism enabling existing fields to be +marked as `Non-Null` for clients that opt out of error propagation without +changing the error propagation boundaries for deployed legacy clients. +Implementations are not required to support this feature, but doing so enables +gradual migration toward semantic nullability while preserving compatibility. + ## Overview -_This appendix defines an optional mechanism for marking fields as -"transitionally non-null" to allow schema evolution without breaking error -behavior for legacy clients. Implementations are not required to support this -feature, but doing so enables gradual migration toward semantic nullability -while preserving compatibility._ The introduction of _error behavior_ to this specification allows clients to take responsibility for error handling, no longer having the schema perform From ab61cd71fdfa2ec78a6622e44181c4d8d809837a Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 30 Apr 2025 16:59:23 +0100 Subject: [PATCH 4/5] Overhaul overview --- spec/Appendix C -- Transitional Non-Null.md | 39 +++++++++------------ 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/spec/Appendix C -- Transitional Non-Null.md b/spec/Appendix C -- Transitional Non-Null.md index 497550359..1eb96c6f8 100644 --- a/spec/Appendix C -- Transitional Non-Null.md +++ b/spec/Appendix C -- Transitional Non-Null.md @@ -8,28 +8,23 @@ gradual migration toward semantic nullability while preserving compatibility. ## Overview - -The introduction of _error behavior_ to this specification allows clients to -take responsibility for error handling, no longer having the schema perform -error propagation and destroying potentially useful response data in the -process. With this move towards clients handling errors, designers of new -schemas (or new fields in existing schemas) no longer need to factor whether or -not a field is likely to error into its nullability; designers can mark a -_semantically_ non-nullable _response position_ (a place where {null} is not a -semantically valid value for the data) as `Non-Null`, writing a {null} there on -error in the knowledge that the client now takes responsibility for handling -errors and preventing these placeholder {null} values from being read. - -However, for schema designers that need to support legacy clients that do not -exhibit these error handling properties, marking semantically non-nullable -response positions as `Non-Null` would mean that more of the response would be -destroyed for these clients on error, potentially turning local widget errors -into full screen errors. - -To allow you to add `Non-Null` to existing fields during this transitional time, -whilst the fields are still in use by legacy clients, without changing their -error propagation boundaries, this appendix introduces the optional -`@noPropagate` directive. +With the introduction of _error behavior_, clients can take responsibility for +handling of _execution error_: correlating {"errors"} in the result with `null` +values inside {"data"} and thereby removing the ambiguity that error propagation +originally set out to solve. If all clients adopt this approach then schema +designers can, and should, reflect true nullability in the schema, marking +fields as `Non-Null` based on their data semantics without regard to whether or +not they might error. + +However, legacy clients may not perform this correlation. Introducing `Non-Null` +in such cases could cause errors to propagate further, potentially turning a +previously handled error in a single field into a full-screen error in the +application. + +To support a smooth transition, this appendix introduces the `@noPropagate` +directive and the concept of _transitional_ Non-Null types. These wrappers raise +errors like regular `Non-Null` types, but suppress propagation and appear +nullable in introspection when using the legacy {"PROPAGATE"} _error behavior_. ## The @noPropagate Directive From 872d58a34b04bba5dffdd9278f3848c25a5c4453 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 30 Apr 2025 17:00:25 +0100 Subject: [PATCH 5/5] Tweak titles --- spec/Appendix C -- Transitional Non-Null.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/Appendix C -- Transitional Non-Null.md b/spec/Appendix C -- Transitional Non-Null.md index 1eb96c6f8..82e09446c 100644 --- a/spec/Appendix C -- Transitional Non-Null.md +++ b/spec/Appendix C -- Transitional Non-Null.md @@ -85,7 +85,7 @@ that behaves identically to Non-Null with two exceptions: 2. When the _error behavior_ of the request is {"PROPAGATE"}, this _response position_ must be exposed as nullable in introspection. -### Changes: Handling Execution Errors +### Changes to Handling Execution Errors When interpreting the [Handling Execution Errors](#sec-Handling-Execution-Errors) and @@ -95,7 +95,7 @@ if they were nullable types. This does not apply to {CompleteValue()} which should still raise an _execution error_ if {null} is returned for a Transitional Non-Null type. -### Changes: Introspection +### Changes to Introspection Note: Transitional Non-Null types do not appear in the type system as a distinct \_\_TypeKind. They are unwrapped to nullable types in introspection when the @@ -122,7 +122,7 @@ The list must match the `levels` that would be passed to `@noPropagate` to describe the field’s transitional Non-Null wrappers, or `null` if no `@noPropagate` would be needed. It must not be an empty list. -### Changes: Type System +### Changes to the Type System When representing a GraphQL schema using the type system definition language, any field whose return type involves Transitional Non-Null types must indicate