From 4cde353bfadfbc11b36dbc9e10e1ccf831399308 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 5 Oct 2023 12:14:11 +0100 Subject: [PATCH 1/5] Directive proposal for opting out of null bubbling --- spec/Section 3 -- Type System.md | 96 +++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 5b7248d2b..5d222385d 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -2025,7 +2025,8 @@ by a validator, executor, or client tool such as a code generator. :: A _built-in directive_ is any directive defined within this specification. -GraphQL implementations should provide the `@skip` and `@include` directives. +GraphQL implementations should provide the `@skip`, `@include` and +`@nullOnError` directives. GraphQL implementations that support the type system definition language must provide the `@deprecated` directive if representing deprecated portions of the @@ -2247,3 +2248,96 @@ to the relevant IETF specification. ```graphql example scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122") ``` + +### @nullOnError + +```graphql +directive @nullOnError on QUERY | MUTATION | SUBSCRIPTION +``` + +The `@nullOnError` _built-in directive_ may be provided on query, mutation and +subscription operations, and disables the error propagation behavior described +in [Handling Field Errors](#sec-Handling-Field-Errors) by treating all Non-Null +types as if they were instead Null-Only-On-Error types. + +Note: This is useful for clients that still wish to receive sibling fields when +an error on a Non-Null value occurs. Effectively, `@nullOnError` enables the +client to opt in to handling errors locally; for example, a client might use +this to limit the scope of null propagation to a fragment rather than the entire +field, or to update a normalized store even when an error occurs. + +Consider the following schema: + +```graphql +type Query { + me: Viewer +} + +type Viewer { + username: String! + bestFriend: Viewer! +} +``` + +If the `bestFriend` field were to return `null`, then the following operation: + +```graphql example +query myQuery { + me { + username + bestFriend { + username + } + } +} +``` + +Would return a result such as: + +```json example +{ + "errors": [ + { + "message": "Value cannot be null", + "locations": [{ "line": 4, "column": 5 }], + "path": ["me", "bestFriend"] + } + ], + "data": { + "me": null + } +} +``` + +However, if we apply the `@nullOnError` directive to our operation: + +```graphql example +query myQuery @nullOnError { + me { + username + bestFriend { + username + } + } +} +``` + +The result would contain identical errors, but the "me" field will be populated: + +```json example +{ + "errors": [ + { + "message": "Value cannot be null", + "locations": [{ "line": 4, "column": 5 }], + "path": ["me", "bestFriend"] + } + ], + "data": { + "me": { + "username": "billy", + "bestFriend": null + } + } +} +``` From 107e9fd2974c862e44235c77191a4f788b7ed544 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 10 Mar 2025 10:18:38 +0000 Subject: [PATCH 2/5] Rename directive to preferred name --- spec/Section 3 -- Type System.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 5d222385d..ac9a8c9b9 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -2026,7 +2026,7 @@ by a validator, executor, or client tool such as a code generator. :: A _built-in directive_ is any directive defined within this specification. GraphQL implementations should provide the `@skip`, `@include` and -`@nullOnError` directives. +`@disableErrorPropagation` directives. GraphQL implementations that support the type system definition language must provide the `@deprecated` directive if representing deprecated portions of the @@ -2249,22 +2249,22 @@ to the relevant IETF specification. scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122") ``` -### @nullOnError +### @disableErrorPropagation ```graphql -directive @nullOnError on QUERY | MUTATION | SUBSCRIPTION +directive @disableErrorPropagation on QUERY | MUTATION | SUBSCRIPTION ``` -The `@nullOnError` _built-in directive_ may be provided on query, mutation and -subscription operations, and disables the error propagation behavior described -in [Handling Field Errors](#sec-Handling-Field-Errors) by treating all Non-Null -types as if they were instead Null-Only-On-Error types. +The `@disableErrorPropagation` _built-in directive_ may be provided on query, +mutation and subscription operations, and disables the error propagation +behavior described in [Handling Field Errors](#sec-Handling-Field-Errors) by +treating all Non-Null types as if they were instead Null-Only-On-Error types. Note: This is useful for clients that still wish to receive sibling fields when -an error on a Non-Null value occurs. Effectively, `@nullOnError` enables the -client to opt in to handling errors locally; for example, a client might use -this to limit the scope of null propagation to a fragment rather than the entire -field, or to update a normalized store even when an error occurs. +an error on a Non-Null value occurs. Effectively, `@disableErrorPropagation` +enables the client to opt in to handling errors locally; for example, a client +might use this to limit the scope of null propagation to a fragment rather than +the entire field, or to update a normalized store even when an error occurs. Consider the following schema: @@ -2309,10 +2309,10 @@ Would return a result such as: } ``` -However, if we apply the `@nullOnError` directive to our operation: +However, if we apply the `@disableErrorPropagation` directive to our operation: ```graphql example -query myQuery @nullOnError { +query myQuery @disableErrorPropagation { me { username bestFriend { From ac3a6ca9788d5c1ef7e95707ebe014ccf17f9d42 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 10 Mar 2025 10:25:24 +0000 Subject: [PATCH 3/5] Add execution logic referencing the directive Reflects the implementation in https://github.com/graphql/graphql-js/pull/4192/files#diff-1d705c6a4c73cd3ce46190029e75abd3015b49de3ec250357dcf9b44a35cb7d0 --- spec/Section 6 -- Execution.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 4e7bd0571..5b75e51fb 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -792,14 +792,19 @@ added to the {"errors"} list in the response, the {"errors"} list must not be further affected. That is, only one error should be added to the errors list per field. -Since `Non-Null` type fields cannot be {null}, field errors are propagated to be +If the operation provides the `@disableErrorPropagation` directive then +`Non-Null` type fields will become {null} if an error occurs. + +If the operation does not provide the `@disableErrorPropagation` directive then +`Non-Null` type fields cannot be {null}, and field errors are propagated to be handled by the parent field. If the parent field may be {null} then it resolves to {null}, otherwise if it is a `Non-Null` type, the field error is further propagated to its parent field. If a `List` type wraps a `Non-Null` type, and one of the elements of that list resolves to {null}, then the entire list must resolve to {null}. If the `List` -type is also wrapped in a `Non-Null`, the field error continues to propagate +type is also wrapped in a `Non-Null` and the operation does not provide the +`@disableErrorPropagation` directive, the field error continues to propagate upwards. If all fields from the root of the request to the source of the field error From 27ab6a11c8851a8fcd7f0005d7156a25e2c995ca Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 10 Mar 2025 10:35:38 +0000 Subject: [PATCH 4/5] Rename Null-Only-On-Error to Semantic-Non-Null --- spec/Section 3 -- Type System.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index ac9a8c9b9..1ed0d08cd 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -2258,7 +2258,7 @@ directive @disableErrorPropagation on QUERY | MUTATION | SUBSCRIPTION The `@disableErrorPropagation` _built-in directive_ may be provided on query, mutation and subscription operations, and disables the error propagation behavior described in [Handling Field Errors](#sec-Handling-Field-Errors) by -treating all Non-Null types as if they were instead Null-Only-On-Error types. +treating all Non-Null types as if they were instead Semantic-Non-Null types. Note: This is useful for clients that still wish to receive sibling fields when an error on a Non-Null value occurs. Effectively, `@disableErrorPropagation` From f844dabcd05508d2f042757540e885aca439a3f2 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Mon, 10 Mar 2025 10:41:45 +0000 Subject: [PATCH 5/5] Missed a bit --- spec/Section 6 -- Execution.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 5b75e51fb..5594b2814 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -801,7 +801,8 @@ handled by the parent field. If the parent field may be {null} then it resolves to {null}, otherwise if it is a `Non-Null` type, the field error is further propagated to its parent field. -If a `List` type wraps a `Non-Null` type, and one of the elements of that list +If a `List` type wraps a `Non-Null` type, the operation does not provide the +`@disableErrorPropagation` directive, and one of the elements of that list resolves to {null}, then the entire list must resolve to {null}. If the `List` type is also wrapped in a `Non-Null` and the operation does not provide the `@disableErrorPropagation` directive, the field error continues to propagate