From 8a13c8f5f66821fc78c693add2326908d948a458 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 18 Oct 2021 14:51:58 -0700 Subject: [PATCH 01/21] initial changes --- spec/Appendix B -- Grammar Summary.md | 2 +- spec/Section 7 -- Response.md | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index 7afe9e11b..02835dc8a 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -48,7 +48,7 @@ Token :: - FloatValue - StringValue -Punctuator :: one of ! $ & ( ) ... : = @ [ ] { | } +Punctuator :: one of ! ? $ & ( ) ... : = @ [ ] { | } Name :: diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index 8ddaaebf6..3234db3df 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -160,10 +160,11 @@ The response might look like: } ``` -If the field which experienced an error was declared as `Non-Null`, the `null` -result will bubble up to the next nullable field. In that case, the `path` for -the error should include the full path to the result field where the error was -raised, even if that field is not present in the response. +If the field which experienced an error was declared as `Non-Null` or designated +`Non-Null` in the query document, the `null` result will propagate to the +next nullable field. In that case, the `path` for the error should include +the full path to the result field where the error was raised, even if that +field is not present in the response. For example, if the `name` field from above had declared a `Non-Null` return type in the schema, the result would look different but the error reported would From c07d86c7f40bb71c427520558c84d409a14f3e54 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 18 Oct 2021 15:03:10 -0700 Subject: [PATCH 02/21] another question mark --- spec/Section 2 -- Language.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index bd5e1c3d5..1241d8a74 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -177,7 +177,7 @@ characters are permitted between the characters defining a {FloatValue}. ### Punctuators -Punctuator :: one of ! $ & ( ) ... : = @ [ ] { | } +Punctuator :: one of ! ? $ & ( ) ... : = @ [ ] { | } GraphQL documents include punctuation in order to describe structure. GraphQL is a data description language and not a programming language, therefore GraphQL From ac03e0e1c288c2b1a29336ea5889c56a0cb604c8 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 18 Oct 2021 21:20:14 -0700 Subject: [PATCH 03/21] describe how desegnators are used --- spec/Section 2 -- Language.md | 67 ++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 1241d8a74..7bbf3817a 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -351,7 +351,7 @@ selection set. Selection sets may also contain fragment references. ## Fields -Field : Alias? Name Arguments? Directives? SelectionSet? +Field : Alias? Name Arguments? Nullability? Directives? SelectionSet? A selection set is primarily composed of fields. A field describes one discrete piece of information available to request within a selection set. @@ -515,6 +515,71 @@ which returns the result: } ``` +## Nullability + +Name Nullability? + +Fields can have their nullability designated with either a `!` to indicate that a +field should be `Non-Nullable` or a `?` to indicate that a field should be +`Nullable`. These designators override the nullability set on a field by the schema +for the operation where they're being used. + +In this example, we can indicate that a `user`'s `name` that could possibly be +`null`, should not be `null`: + +```graphql example +{ + user(id: 4) { + id + name! + } +} +``` + +If `name` comes back non-`null`, then the return value is the same as if the +nullability designator was not used: + +```json example +{ + "user": { + "id": 4, + "name": "Mark Zuckerberg" + } +} +``` + +In the event that `name` is `null`, the field's parent selection set becomes `null` +in the result and an error is returned, just as if `name` was marked `Non-Nullable` +in the schema: + +```json example +{ + "data": { + "user": null + }, + "errors": [ + { + "locations": [{ "column": 13, "line": 4 }], + "message": "Cannot return null for non-nullable field User.name.", + "path": ["user", "name"], + }, + ] +} +``` + +If `user` was `Non-Nullable` in the schema, but we don't want `null`s bubbling past +that point, then we can use `?` as an error boundary. `User` will be treated as +`Nullable` for this operation: + +```graphql example +{ + user(id: 4)? { + id + name! + } +} +``` + ## Fragments FragmentSpread : ... FragmentName Directives? From 155e09a3b9688d745518befb431db9fd47be8d6f Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 18 Oct 2021 21:49:10 -0700 Subject: [PATCH 04/21] fix context-free grammar notation --- spec/Appendix B -- Grammar Summary.md | 4 +++- spec/Section 2 -- Language.md | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index 02835dc8a..33660e42a 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -156,10 +156,12 @@ Selection : - FragmentSpread - InlineFragment -Field : Alias? Name Arguments? Directives? SelectionSet? +Field : Alias? Name Arguments? Nullability? Directives? SelectionSet? Alias : Name : +Nullability : (`?`|`!`)? + Arguments[Const] : ( Argument[?Const]+ ) Argument[Const] : Name : Value[?Const] diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 7bbf3817a..13350e24f 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -517,7 +517,7 @@ which returns the result: ## Nullability -Name Nullability? +Nullability : (`?`|`!`)? Fields can have their nullability designated with either a `!` to indicate that a field should be `Non-Nullable` or a `?` to indicate that a field should be From e99db4cb90568669f5b3721d90bfc04331f6d2d6 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 18 Oct 2021 21:56:10 -0700 Subject: [PATCH 05/21] remove ? from grammar spec --- spec/Appendix B -- Grammar Summary.md | 2 +- spec/Section 2 -- Language.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index 33660e42a..3a1d96ff6 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -160,7 +160,7 @@ Field : Alias? Name Arguments? Nullability? Directives? SelectionSet? Alias : Name : -Nullability : (`?`|`!`)? +Nullability : (`?`|`!`) Arguments[Const] : ( Argument[?Const]+ ) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 13350e24f..5d5eb0ef2 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -517,7 +517,7 @@ which returns the result: ## Nullability -Nullability : (`?`|`!`)? +Nullability : (`?`|`!`) Fields can have their nullability designated with either a `!` to indicate that a field should be `Non-Nullable` or a `?` to indicate that a field should be From 1677f623159a5f15c0eda54eb6d82dc8cb82621c Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 18 Oct 2021 22:22:16 -0700 Subject: [PATCH 06/21] validation --- spec/Section 5 -- Validation.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 4eda8e7b4..9dd13bb1c 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -564,6 +564,21 @@ fragment conflictingDifferingResponses on Pet { } ``` +The same is true if a field is designated `Non-Nullable` in an operation. In this +case, `someValue` could be either a `String` or a `String!` which are two different +types and therefor can not be merged: + +```graphql counter-example +fragment conflictingDifferingResponses on Pet { + ... on Dog { + someValue: nickname + } + ... on Cat { + someValue: nickname! + } +} +``` + ### Leaf Field Selections **Formal Specification** From ce80aa9dca74231bf5a8775e5ee73d081159b532 Mon Sep 17 00:00:00 2001 From: Alex Reilly Date: Thu, 21 Oct 2021 11:01:42 -0700 Subject: [PATCH 07/21] Update spec/Section 2 -- Language.md Co-authored-by: Benjie Gillam --- spec/Section 2 -- Language.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 5d5eb0ef2..015545a41 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -517,7 +517,9 @@ which returns the result: ## Nullability -Nullability : (`?`|`!`) +Nullability : + - ! + - ? Fields can have their nullability designated with either a `!` to indicate that a field should be `Non-Nullable` or a `?` to indicate that a field should be From 4743051979d840b9d06cbb75834e25b0b2bff452 Mon Sep 17 00:00:00 2001 From: Alex Reilly Date: Thu, 21 Oct 2021 11:01:53 -0700 Subject: [PATCH 08/21] Update spec/Appendix B -- Grammar Summary.md Co-authored-by: Benjie Gillam --- spec/Appendix B -- Grammar Summary.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index 3a1d96ff6..d2cfa21d4 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -160,7 +160,9 @@ Field : Alias? Name Arguments? Nullability? Directives? SelectionSet? Alias : Name : -Nullability : (`?`|`!`) +Nullability : + - ! + - ? Arguments[Const] : ( Argument[?Const]+ ) From f392add1d12daac3091b7af623dfbfbfded86105 Mon Sep 17 00:00:00 2001 From: twof Date: Thu, 21 Oct 2021 17:14:21 -0700 Subject: [PATCH 09/21] using propagating instead of bubbling --- spec/Section 2 -- Language.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 015545a41..3b04c08b4 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -569,9 +569,9 @@ in the schema: } ``` -If `user` was `Non-Nullable` in the schema, but we don't want `null`s bubbling past -that point, then we can use `?` as an error boundary. `User` will be treated as -`Nullable` for this operation: +If `user` was `Non-Nullable` in the schema, but we don't want `null`s propagating +past that point, then we can use `?` to create an error boundary. `User` will be +treated as `Nullable` for this operation: ```graphql example { From 1473f78d4349cecf546d5d221e211759a46e2981 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 25 Oct 2021 12:26:01 -0700 Subject: [PATCH 10/21] further clarify designator semantics --- spec/Section 2 -- Language.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 3b04c08b4..82cd4acee 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -524,7 +524,9 @@ Nullability : Fields can have their nullability designated with either a `!` to indicate that a field should be `Non-Nullable` or a `?` to indicate that a field should be `Nullable`. These designators override the nullability set on a field by the schema -for the operation where they're being used. +for the operation where they're being used. For example, a field marked with `!` in +a query will be treated identically to a field marked with `!` in the schema for the +purposes of validation and execution. In this example, we can indicate that a `user`'s `name` that could possibly be `null`, should not be `null`: From 432555d37a8ccf9054ed34982760cf46413aa4e7 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 1 Nov 2021 22:30:59 -0700 Subject: [PATCH 11/21] introduce modified field types --- spec/Section 6 -- Execution.md | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index a0f3400d4..714cf614e 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -568,14 +568,26 @@ set or coercing a scalar value. ExecuteField(objectType, objectValue, fieldType, fields, variableValues): -- Let {field} be the first entry in {fields}. -- Let {fieldName} be the field name of {field}. -- Let {argumentValues} be the result of {CoerceArgumentValues(objectType, field, - variableValues)} -- Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName, - argumentValues)}. -- Return the result of {CompleteValue(fieldType, fields, resolvedValue, - variableValues)}. + - Let {field} be the first entry in {fields}. + - Let {fieldName} be the field name of {field}. + - Let {requiredStatus} be the required status of {field}. + - Let {argumentValues} be the result of {CoerceArgumentValues(objectType, field, variableValues)} + - Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName, argumentValues)}. + - Let {modifiedFieldType} be {ModifiedOutputType(fieldType, requiredStatus)}. + - Return the result of {CompleteValue(modifiedFieldType, fields, resolvedValue, variableValues)}. + +ModifiedOutputType(outputType, requiredStatus): + + - If {requiredStatus} is 'required' and {outputType} is not a Non-Nullable type: + - Return Non-Null with an inner type of {outputType}. + - Otherwise if {requiredStatus} is 'optional': + - If {outputType} is not a Non-Nullable type: + - Return {outputType}. + - Otherwise if {outputType} is a Non-Nullable type: + - Let {innerOutputType} be the inner type of {outputType}. + - Return {innerOutputType}. + - Otherwise: + - Return {outputType}. ### Coercing Field Arguments From 19546803bc528c320e18f50ebc6d93cae63e8a42 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 1 Nov 2021 23:22:29 -0700 Subject: [PATCH 12/21] section and description --- spec/Section 6 -- Execution.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 714cf614e..f1891b52e 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -576,6 +576,13 @@ ExecuteField(objectType, objectValue, fieldType, fields, variableValues): - Let {modifiedFieldType} be {ModifiedOutputType(fieldType, requiredStatus)}. - Return the result of {CompleteValue(modifiedFieldType, fields, resolvedValue, variableValues)}. +## Accounting For Client Controlled Nullability Designators + +A field can have its nullability status set either in its service's schema, or +it can be overriden by a designator (! or ?) for the duration of an execution. +In order to determine a field's true nullability, both are taken into account +and a final type is produced. + ModifiedOutputType(outputType, requiredStatus): - If {requiredStatus} is 'required' and {outputType} is not a Non-Nullable type: From 3cc7af5fd788d3ccfb326bf2f6447d436ebdc91f Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 1 Nov 2021 23:47:05 -0700 Subject: [PATCH 13/21] errors and validation formal definitions --- spec/Section 5 -- Validation.md | 29 +++++++++++++++++++++++++++++ spec/Section 6 -- Execution.md | 10 +++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 9dd13bb1c..cd70a3995 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -426,6 +426,7 @@ FieldsInSetCanMerge(set): SameResponseShape(fieldA, fieldB): +<<<<<<< HEAD - Let {typeA} be the return type of {fieldA}. - Let {typeB} be the return type of {fieldB}. - If {typeA} or {typeB} is Non-Null. @@ -448,6 +449,34 @@ SameResponseShape(fieldA, fieldB): - Given each pair of members {subfieldA} and {subfieldB} in {fieldsForName}: - If {SameResponseShape(subfieldA, subfieldB)} is false, return false. - Return true. +======= + * Let {typeA} be the return type of {fieldA}. + * Let {typeB} be the return type of {fieldB}. + * Let {fieldARequiredStatus} be the required status of {fieldA} + * Let {fieldBRequiredStatus} be the required status of {fieldB} + * Let {typeA} be the result of {ModifiedOutputType(typeA, fieldARequiredStatus)} + * Let {typeB} be the result of {ModifiedOutputType(typeB, fieldBRequiredStatus)} + * If {typeA} or {typeB} is Non-Null. + * If {typeA} or {typeB} is nullable, return false. + * Let {typeA} be the nullable type of {typeA} + * Let {typeB} be the nullable type of {typeB} + * If {typeA} or {typeB} is List. + * If {typeA} or {typeB} is not List, return false. + * Let {typeA} be the item type of {typeA} + * Let {typeB} be the item type of {typeB} + * Repeat from step 3. + * If {typeA} or {typeB} is Scalar or Enum. + * If {typeA} and {typeB} are the same type return true, otherwise return + false. + * Assert: {typeA} and {typeB} are both composite types. + * Let {mergedSet} be the result of adding the selection set of {fieldA} and + the selection set of {fieldB}. + * Let {fieldsForName} be the set of selections with a given response name in + {mergedSet} including visiting fragments and inline fragments. + * Given each pair of members {subfieldA} and {subfieldB} in {fieldsForName}: + * If {SameResponseShape(subfieldA, subfieldB)} is false, return false. + * Return true. +>>>>>>> 9906241 (errors and validation formal definitions) **Explanatory Text** diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index f1891b52e..6ac0ed2d0 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -797,9 +797,9 @@ field returned {null}, and the error must be added to the {"errors"} list in the response. If the result of resolving a field is {null} (either because the function to -resolve the field returned {null} or because a field error was raised), and that -field is of a `Non-Null` type, then a field error is raised. The error must be -added to the {"errors"} list in the response. +resolve the field returned {null} or because a field error was raised), and +the {ModifiedOutputType} of that field is of a `Non-Null` type, then a field +error is raised. The error must be added to the {"errors"} list in the response. If the field returns {null} because of a field error which has already been added to the {"errors"} list in the response, the {"errors"} list must not be @@ -808,8 +808,8 @@ field. Since `Non-Null` type fields cannot be {null}, 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. +to {null}, otherwise if its {ModifiedOutputType} 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` From 27820997a94f98c2b9ebdc548f89253b574b77f4 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 20 Dec 2021 11:43:46 -0800 Subject: [PATCH 14/21] language list syntax --- spec/Section 2 -- Language.md | 36 ++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 82cd4acee..674ee868c 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -518,8 +518,14 @@ which returns the result: ## Nullability Nullability : - - ! - - ? + - ListNullability NullabilityModifier? + - NullabilityModifier + +ListNullability : `[` Nullability? `]` + +NullabilityModifier : + - `!` + - `?` Fields can have their nullability designated with either a `!` to indicate that a field should be `Non-Nullable` or a `?` to indicate that a field should be @@ -572,7 +578,7 @@ in the schema: ``` If `user` was `Non-Nullable` in the schema, but we don't want `null`s propagating -past that point, then we can use `?` to create an error boundary. `User` will be +past that point, then we can use `?` to create null propagation boundary. `User` will be treated as `Nullable` for this operation: ```graphql example @@ -584,6 +590,30 @@ treated as `Nullable` for this operation: } ``` +Nullability designators can also be applied to list elements like so. + +```graphql example +{ + user(id: 4)? { + id + petsNames[!]? + } +} +``` + +In the above example, the query author is saying that each individual pet name should be +`Non-Nullable`, but the list as a whole should be `Nullable`. The same syntax can be +applied to multidimensional lists. + +```graphql example +{ + threeDimensionalMatrix[[[?]!]]! +} + +Any element without a nullability designator will inherit its nullability from the schema definition, exactly the same as non-list fields do. The number of dimensions indicated by +list element nullability syntax is required to match the number of dimensions of the field. +Anything else results in a query validation error. + ## Fragments FragmentSpread : ... FragmentName Directives? From 6e615bcd63c52c4e2e3870c32d9171189abacbb5 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 20 Dec 2021 20:22:44 -0800 Subject: [PATCH 15/21] response and validation --- spec/Section 5 -- Validation.md | 50 ++++++++++++++------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index cd70a3995..d8f14843f 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -426,7 +426,6 @@ FieldsInSetCanMerge(set): SameResponseShape(fieldA, fieldB): -<<<<<<< HEAD - Let {typeA} be the return type of {fieldA}. - Let {typeB} be the return type of {fieldB}. - If {typeA} or {typeB} is Non-Null. @@ -449,34 +448,6 @@ SameResponseShape(fieldA, fieldB): - Given each pair of members {subfieldA} and {subfieldB} in {fieldsForName}: - If {SameResponseShape(subfieldA, subfieldB)} is false, return false. - Return true. -======= - * Let {typeA} be the return type of {fieldA}. - * Let {typeB} be the return type of {fieldB}. - * Let {fieldARequiredStatus} be the required status of {fieldA} - * Let {fieldBRequiredStatus} be the required status of {fieldB} - * Let {typeA} be the result of {ModifiedOutputType(typeA, fieldARequiredStatus)} - * Let {typeB} be the result of {ModifiedOutputType(typeB, fieldBRequiredStatus)} - * If {typeA} or {typeB} is Non-Null. - * If {typeA} or {typeB} is nullable, return false. - * Let {typeA} be the nullable type of {typeA} - * Let {typeB} be the nullable type of {typeB} - * If {typeA} or {typeB} is List. - * If {typeA} or {typeB} is not List, return false. - * Let {typeA} be the item type of {typeA} - * Let {typeB} be the item type of {typeB} - * Repeat from step 3. - * If {typeA} or {typeB} is Scalar or Enum. - * If {typeA} and {typeB} are the same type return true, otherwise return - false. - * Assert: {typeA} and {typeB} are both composite types. - * Let {mergedSet} be the result of adding the selection set of {fieldA} and - the selection set of {fieldB}. - * Let {fieldsForName} be the set of selections with a given response name in - {mergedSet} including visiting fragments and inline fragments. - * Given each pair of members {subfieldA} and {subfieldB} in {fieldsForName}: - * If {SameResponseShape(subfieldA, subfieldB)} is false, return false. - * Return true. ->>>>>>> 9906241 (errors and validation formal definitions) **Explanatory Text** @@ -608,6 +579,27 @@ fragment conflictingDifferingResponses on Pet { } ``` +### Client Controlled Nullability Designator List Dimensions + +**Formal Specification** + +* For each {field} in the document + * Let {fieldDef} be the definition of {field} + * Let {fieldType} be the type of {fieldDef} + * Let {requiredStatus} be the required status of {field} + * Let {designatorDepth} be the number of square bracket pairs in {requiredStatus} + * Let {typeDepth} be the number of list dimensions in {fieldType} + * If {typeDepth} equals {designatorDepth} return true + * Otherwise return false + +**Explanatory Text** + +List fields can be marked with nullability designators that look like `[?]!` to indicate the +nullability of the list's elements and the nullability of the list itself. For multi-dimensional +lists, the designator would look something like `[[[!]?]]!`. The number of dimensions of the +designator are required to match the number of dimensions of the field's type. If the two do not +match then a validation error is thrown. + ### Leaf Field Selections **Formal Specification** From 0fbd45672a5c65ec7720e81f6980f35ff2c72441 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 20 Dec 2021 21:07:45 -0800 Subject: [PATCH 16/21] list execution --- spec/Section 6 -- Execution.md | 52 +++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 6ac0ed2d0..3475ce208 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -579,22 +579,52 @@ ExecuteField(objectType, objectValue, fieldType, fields, variableValues): ## Accounting For Client Controlled Nullability Designators A field can have its nullability status set either in its service's schema, or -it can be overriden by a designator (! or ?) for the duration of an execution. +a nullability designator (! or ?) can override it for the duration of an execution. In order to determine a field's true nullability, both are taken into account and a final type is produced. ModifiedOutputType(outputType, requiredStatus): - - If {requiredStatus} is 'required' and {outputType} is not a Non-Nullable type: - - Return Non-Null with an inner type of {outputType}. - - Otherwise if {requiredStatus} is 'optional': - - If {outputType} is not a Non-Nullable type: - - Return {outputType}. - - Otherwise if {outputType} is a Non-Nullable type: - - Let {innerOutputType} be the inner type of {outputType}. - - Return {innerOutputType}. - - Otherwise: - - Return {outputType}. + - Create a {stack} initially containing {type}. + - As long as the top of {stack} is a list: + - Let {currentType} be the top item of {stack}. + - Push the {elementType} of {currentType} to the {stack}. + - If {requiredStatus} exists: + - Start visiting {node}s in {requiredStatus} and building up a {resultingType}: + - For each {node} that is a RequiredDesignator: + - If {resultingType} exists: + - Let {nullableResult} be the nullable type of {resultingType}. + - Set {resultingType} to the Non-Nullable type of {nullableResult}. + - Continue onto the next node. + - Pop the top of {stack} and let {nextType} be the result. + - Let {nullableType} be the nullable type of {nextType}. + - Set {resultingType} to the Non-Nullable type of {nullableType}. + - Continue onto the next node. + - For each {node} that is a OptionalDesignator: + - If {resultingType} exists: + - Set {resultingType} to the nullableType type of {resultingType}. + - Continue onto the next node. + - Pop the top of {stack} and let {nextType} be the result. + - Set {resultingType} to the nullable type of {resultingType} + - Continue onto the next node. + - For each {node} that is a ListNullabilityDesignator: + - Pop the top of {stack} and let {listType} be the result + - If the nullable type of {listType} is not a list + - Pop the top of {stack} and set {listType} to the result + - If {listType} does not exist: + - Throw an error because {requiredStatus} had more list dimensions than {outputType} and is invalid. + - If {resultingType} exist: + - If {listType} is Non-Nullable: + - Set {resultingType} to a Non-Nullable list where the element is {resultingType}. + - Otherwise: + - Set {resultingType} to a list where the element is {resultingType}. + - Continue onto the next node. + - Set {resultingType} to {listType} + - If {stack} is not empty: + - Throw an error because {requiredStatus} had fewer list dimensions than {outputType} and is invalid. + - Return {resultingType}. +- Otherwise: + - Return {outputType}. ### Coercing Field Arguments From f9f5e2f433190148d532aa421d3f1a75b267a6bc Mon Sep 17 00:00:00 2001 From: Alex Reilly Date: Tue, 18 Jan 2022 13:44:42 -0800 Subject: [PATCH 17/21] Spelling fix --- spec/Section 5 -- Validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index d8f14843f..79687684c 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -566,7 +566,7 @@ fragment conflictingDifferingResponses on Pet { The same is true if a field is designated `Non-Nullable` in an operation. In this case, `someValue` could be either a `String` or a `String!` which are two different -types and therefor can not be merged: +types and therefore can not be merged: ```graphql counter-example fragment conflictingDifferingResponses on Pet { From b24ce32596d4f8b6e52f664ddf39da915c0eda66 Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 14 Mar 2022 14:28:02 -0700 Subject: [PATCH 18/21] language updated --- spec/Section 2 -- Language.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 674ee868c..5ee996262 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -530,16 +530,17 @@ NullabilityModifier : Fields can have their nullability designated with either a `!` to indicate that a field should be `Non-Nullable` or a `?` to indicate that a field should be `Nullable`. These designators override the nullability set on a field by the schema -for the operation where they're being used. For example, a field marked with `!` in -a query will be treated identically to a field marked with `!` in the schema for the -purposes of validation and execution. +for the operation where they're being used. In addition to being `Non-Nullable`, +if a field marked with `!` resolves to `null`, it propagates to the nearest parent +field marked with a `?` or to `data` if one does not exist. An error is added to +the `errors` array identical to if the field had been `Non-Nullable` in the schema. In this example, we can indicate that a `user`'s `name` that could possibly be -`null`, should not be `null`: +`null`, should not be `null` and that `null` propagation should halt at the `user` field: ```graphql example { - user(id: 4) { + user(id: 4)? { id name! } @@ -609,8 +610,13 @@ applied to multidimensional lists. { threeDimensionalMatrix[[[?]!]]! } +``` -Any element without a nullability designator will inherit its nullability from the schema definition, exactly the same as non-list fields do. The number of dimensions indicated by +Any element without a nullability designator will inherit its nullability from the +schema definition, exactly the same as non-list fields do. When designating +nullability for list fields, query authors can either use a single designator (`!` or `?`) +to designate the nullability of the entire field, or they can use the list element +nullability syntax displayed above. The number of dimensions indicated by list element nullability syntax is required to match the number of dimensions of the field. Anything else results in a query validation error. From 06819e3fe8a3bc740ef9e3f70e9e6651e27dc72f Mon Sep 17 00:00:00 2001 From: twof Date: Mon, 14 Mar 2022 20:30:30 -0700 Subject: [PATCH 19/21] validation --- spec/Section 5 -- Validation.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 79687684c..3b166dd4c 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -583,20 +583,20 @@ fragment conflictingDifferingResponses on Pet { **Formal Specification** -* For each {field} in the document - * Let {fieldDef} be the definition of {field} - * Let {fieldType} be the type of {fieldDef} - * Let {requiredStatus} be the required status of {field} - * Let {designatorDepth} be the number of square bracket pairs in {requiredStatus} - * Let {typeDepth} be the number of list dimensions in {fieldType} - * If {typeDepth} equals {designatorDepth} return true - * Otherwise return false +- For each {field} in the document + - Let {fieldDef} be the definition of {field} + - Let {fieldType} be the type of {fieldDef} + - Let {requiredStatus} be the required status of {field} + - Let {designatorDepth} be the number of square bracket pairs in {requiredStatus} + - Let {typeDepth} be the number of list dimensions in {fieldType} + - If {typeDepth} equals {designatorDepth} or {designatorDepth} equals 0 return true + - Otherwise return false **Explanatory Text** List fields can be marked with nullability designators that look like `[?]!` to indicate the nullability of the list's elements and the nullability of the list itself. For multi-dimensional -lists, the designator would look something like `[[[!]?]]!`. The number of dimensions of the +lists, the designator would look something like `[[[!]?]]!`. If the designator is not a simple `!` or `?`, then the number of dimensions of the designator are required to match the number of dimensions of the field's type. If the two do not match then a validation error is thrown. From 3822c976663fb8b59f559e37c95e693bdbe8620d Mon Sep 17 00:00:00 2001 From: twof Date: Tue, 15 Mar 2022 00:52:19 -0700 Subject: [PATCH 20/21] formatting --- spec/Appendix B -- Grammar Summary.md | 5 +- spec/Section 2 -- Language.md | 68 ++++++++-------- spec/Section 5 -- Validation.md | 23 +++--- spec/Section 6 -- Execution.md | 113 ++++++++++++++------------ spec/Section 7 -- Response.md | 8 +- 5 files changed, 116 insertions(+), 101 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index d2cfa21d4..0f849271b 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -161,8 +161,9 @@ Field : Alias? Name Arguments? Nullability? Directives? SelectionSet? Alias : Name : Nullability : - - ! - - ? + +- ! +- ? Arguments[Const] : ( Argument[?Const]+ ) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 5ee996262..d9ce6653e 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -518,25 +518,29 @@ which returns the result: ## Nullability Nullability : - - ListNullability NullabilityModifier? - - NullabilityModifier + +- ListNullability NullabilityModifier? +- NullabilityModifier ListNullability : `[` Nullability? `]` NullabilityModifier : - - `!` - - `?` -Fields can have their nullability designated with either a `!` to indicate that a -field should be `Non-Nullable` or a `?` to indicate that a field should be -`Nullable`. These designators override the nullability set on a field by the schema -for the operation where they're being used. In addition to being `Non-Nullable`, -if a field marked with `!` resolves to `null`, it propagates to the nearest parent -field marked with a `?` or to `data` if one does not exist. An error is added to -the `errors` array identical to if the field had been `Non-Nullable` in the schema. +- `!` +- `?` + +Fields can have their nullability designated with either a `!` to indicate that +a field should be `Non-Nullable` or a `?` to indicate that a field should be +`Nullable`. These designators override the nullability set on a field by the +schema for the operation where they're being used. In addition to being +`Non-Nullable`, if a field marked with `!` resolves to `null`, it propagates to +the nearest parent field marked with a `?` or to `data` if one does not exist. +An error is added to the `errors` array identical to if the field had been +`Non-Nullable` in the schema. -In this example, we can indicate that a `user`'s `name` that could possibly be -`null`, should not be `null` and that `null` propagation should halt at the `user` field: +In this example, we can indicate that a `user`'s `name` that could possibly be +`null`, should not be `null` and that `null` propagation should halt at the +`user` field: ```graphql example { @@ -547,7 +551,7 @@ In this example, we can indicate that a `user`'s `name` that could possibly be } ``` -If `name` comes back non-`null`, then the return value is the same as if the +If `name` comes back non-`null`, then the return value is the same as if the nullability designator was not used: ```json example @@ -559,9 +563,9 @@ nullability designator was not used: } ``` -In the event that `name` is `null`, the field's parent selection set becomes `null` -in the result and an error is returned, just as if `name` was marked `Non-Nullable` -in the schema: +In the event that `name` is `null`, the field's parent selection set becomes +`null` in the result and an error is returned, just as if `name` was marked +`Non-Nullable` in the schema: ```json example { @@ -572,15 +576,15 @@ in the schema: { "locations": [{ "column": 13, "line": 4 }], "message": "Cannot return null for non-nullable field User.name.", - "path": ["user", "name"], - }, + "path": ["user", "name"] + } ] } ``` -If `user` was `Non-Nullable` in the schema, but we don't want `null`s propagating -past that point, then we can use `?` to create null propagation boundary. `User` will be -treated as `Nullable` for this operation: +If `user` was `Non-Nullable` in the schema, but we don't want `null`s +propagating past that point, then we can use `?` to create null propagation +boundary. `User` will be treated as `Nullable` for this operation: ```graphql example { @@ -602,9 +606,9 @@ Nullability designators can also be applied to list elements like so. } ``` -In the above example, the query author is saying that each individual pet name should be -`Non-Nullable`, but the list as a whole should be `Nullable`. The same syntax can be -applied to multidimensional lists. +In the above example, the query author is saying that each individual pet name +should be `Non-Nullable`, but the list as a whole should be `Nullable`. The same +syntax can be applied to multidimensional lists. ```graphql example { @@ -612,13 +616,13 @@ applied to multidimensional lists. } ``` -Any element without a nullability designator will inherit its nullability from the -schema definition, exactly the same as non-list fields do. When designating -nullability for list fields, query authors can either use a single designator (`!` or `?`) -to designate the nullability of the entire field, or they can use the list element -nullability syntax displayed above. The number of dimensions indicated by -list element nullability syntax is required to match the number of dimensions of the field. -Anything else results in a query validation error. +Any element without a nullability designator will inherit its nullability from +the schema definition, exactly the same as non-list fields do. When designating +nullability for list fields, query authors can either use a single designator +(`!` or `?`) to designate the nullability of the entire field, or they can use +the list element nullability syntax displayed above. The number of dimensions +indicated by list element nullability syntax is required to match the number of +dimensions of the field. Anything else results in a query validation error. ## Fragments diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 3b166dd4c..77d89363f 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -564,9 +564,9 @@ fragment conflictingDifferingResponses on Pet { } ``` -The same is true if a field is designated `Non-Nullable` in an operation. In this -case, `someValue` could be either a `String` or a `String!` which are two different -types and therefore can not be merged: +The same is true if a field is designated `Non-Nullable` in an operation. In +this case, `someValue` could be either a `String` or a `String!` which are two +different types and therefore can not be merged: ```graphql counter-example fragment conflictingDifferingResponses on Pet { @@ -587,18 +587,21 @@ fragment conflictingDifferingResponses on Pet { - Let {fieldDef} be the definition of {field} - Let {fieldType} be the type of {fieldDef} - Let {requiredStatus} be the required status of {field} - - Let {designatorDepth} be the number of square bracket pairs in {requiredStatus} + - Let {designatorDepth} be the number of square bracket pairs in + {requiredStatus} - Let {typeDepth} be the number of list dimensions in {fieldType} - - If {typeDepth} equals {designatorDepth} or {designatorDepth} equals 0 return true + - If {typeDepth} equals {designatorDepth} or {designatorDepth} equals 0 return + true - Otherwise return false **Explanatory Text** -List fields can be marked with nullability designators that look like `[?]!` to indicate the -nullability of the list's elements and the nullability of the list itself. For multi-dimensional -lists, the designator would look something like `[[[!]?]]!`. If the designator is not a simple `!` or `?`, then the number of dimensions of the -designator are required to match the number of dimensions of the field's type. If the two do not -match then a validation error is thrown. +List fields can be marked with nullability designators that look like `[?]!` to +indicate the nullability of the list's elements and the nullability of the list +itself. For multi-dimensional lists, the designator would look something like +`[[[!]?]]!`. If the designator is not a simple `!` or `?`, then the number of +dimensions of the designator are required to match the number of dimensions of +the field's type. If the two do not match then a validation error is thrown. ### Leaf Field Selections diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 3475ce208..e601254ae 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -568,63 +568,70 @@ set or coercing a scalar value. ExecuteField(objectType, objectValue, fieldType, fields, variableValues): - - Let {field} be the first entry in {fields}. - - Let {fieldName} be the field name of {field}. - - Let {requiredStatus} be the required status of {field}. - - Let {argumentValues} be the result of {CoerceArgumentValues(objectType, field, variableValues)} - - Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName, argumentValues)}. - - Let {modifiedFieldType} be {ModifiedOutputType(fieldType, requiredStatus)}. - - Return the result of {CompleteValue(modifiedFieldType, fields, resolvedValue, variableValues)}. +- Let {field} be the first entry in {fields}. +- Let {fieldName} be the field name of {field}. +- Let {requiredStatus} be the required status of {field}. +- Let {argumentValues} be the result of {CoerceArgumentValues(objectType, field, + variableValues)} +- Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName, + argumentValues)}. +- Let {modifiedFieldType} be {ModifiedOutputType(fieldType, requiredStatus)}. +- Return the result of {CompleteValue(modifiedFieldType, fields, resolvedValue, + variableValues)}. ## Accounting For Client Controlled Nullability Designators -A field can have its nullability status set either in its service's schema, or -a nullability designator (! or ?) can override it for the duration of an execution. -In order to determine a field's true nullability, both are taken into account -and a final type is produced. +A field can have its nullability status set either in its service's schema, or a +nullability designator (! or ?) can override it for the duration of an +execution. In order to determine a field's true nullability, both are taken into +account and a final type is produced. ModifiedOutputType(outputType, requiredStatus): - - Create a {stack} initially containing {type}. - - As long as the top of {stack} is a list: - - Let {currentType} be the top item of {stack}. - - Push the {elementType} of {currentType} to the {stack}. - - If {requiredStatus} exists: - - Start visiting {node}s in {requiredStatus} and building up a {resultingType}: - - For each {node} that is a RequiredDesignator: - - If {resultingType} exists: - - Let {nullableResult} be the nullable type of {resultingType}. - - Set {resultingType} to the Non-Nullable type of {nullableResult}. - - Continue onto the next node. - - Pop the top of {stack} and let {nextType} be the result. - - Let {nullableType} be the nullable type of {nextType}. - - Set {resultingType} to the Non-Nullable type of {nullableType}. +- Create a {stack} initially containing {type}. +- As long as the top of {stack} is a list: + - Let {currentType} be the top item of {stack}. + - Push the {elementType} of {currentType} to the {stack}. +- If {requiredStatus} exists: + - Start visiting {node}s in {requiredStatus} and building up a + {resultingType}: + - For each {node} that is a RequiredDesignator: + - If {resultingType} exists: + - Let {nullableResult} be the nullable type of {resultingType}. + - Set {resultingType} to the Non-Nullable type of {nullableResult}. - Continue onto the next node. - - For each {node} that is a OptionalDesignator: - - If {resultingType} exists: - - Set {resultingType} to the nullableType type of {resultingType}. - - Continue onto the next node. - - Pop the top of {stack} and let {nextType} be the result. - - Set {resultingType} to the nullable type of {resultingType} + - Pop the top of {stack} and let {nextType} be the result. + - Let {nullableType} be the nullable type of {nextType}. + - Set {resultingType} to the Non-Nullable type of {nullableType}. + - Continue onto the next node. + - For each {node} that is a OptionalDesignator: + - If {resultingType} exists: + - Set {resultingType} to the nullableType type of {resultingType}. - Continue onto the next node. - - For each {node} that is a ListNullabilityDesignator: - - Pop the top of {stack} and let {listType} be the result - - If the nullable type of {listType} is not a list - - Pop the top of {stack} and set {listType} to the result - - If {listType} does not exist: - - Throw an error because {requiredStatus} had more list dimensions than {outputType} and is invalid. - - If {resultingType} exist: - - If {listType} is Non-Nullable: - - Set {resultingType} to a Non-Nullable list where the element is {resultingType}. - - Otherwise: - - Set {resultingType} to a list where the element is {resultingType}. - - Continue onto the next node. - - Set {resultingType} to {listType} - - If {stack} is not empty: - - Throw an error because {requiredStatus} had fewer list dimensions than {outputType} and is invalid. - - Return {resultingType}. -- Otherwise: - - Return {outputType}. + - Pop the top of {stack} and let {nextType} be the result. + - Set {resultingType} to the nullable type of {resultingType} + - Continue onto the next node. + - For each {node} that is a ListNullabilityDesignator: + - Pop the top of {stack} and let {listType} be the result + - If the nullable type of {listType} is not a list + - Pop the top of {stack} and set {listType} to the result + - If {listType} does not exist: + - Throw an error because {requiredStatus} had more list dimensions than + {outputType} and is invalid. + - If {resultingType} exist: + - If {listType} is Non-Nullable: + - Set {resultingType} to a Non-Nullable list where the element is + {resultingType}. + - Otherwise: + - Set {resultingType} to a list where the element is {resultingType}. + - Continue onto the next node. + - Set {resultingType} to {listType} +- If {stack} is not empty: + - Throw an error because {requiredStatus} had fewer list dimensions than + {outputType} and is invalid. +- Return {resultingType}. +- Otherwise: + - Return {outputType}. ### Coercing Field Arguments @@ -827,9 +834,9 @@ field returned {null}, and the error must be added to the {"errors"} list in the response. If the result of resolving a field is {null} (either because the function to -resolve the field returned {null} or because a field error was raised), and -the {ModifiedOutputType} of that field is of a `Non-Null` type, then a field -error is raised. The error must be added to the {"errors"} list in the response. +resolve the field returned {null} or because a field error was raised), and the +{ModifiedOutputType} of that field is of a `Non-Null` type, then a field error +is raised. The error must be added to the {"errors"} list in the response. If the field returns {null} because of a field error which has already been added to the {"errors"} list in the response, the {"errors"} list must not be @@ -838,7 +845,7 @@ field. Since `Non-Null` type fields cannot be {null}, 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 its {ModifiedOutputType} is a `Non-Null` type, the field +to {null}, otherwise if its {ModifiedOutputType} 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 diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index 3234db3df..d5f308629 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -161,10 +161,10 @@ The response might look like: ``` If the field which experienced an error was declared as `Non-Null` or designated -`Non-Null` in the query document, the `null` result will propagate to the -next nullable field. In that case, the `path` for the error should include -the full path to the result field where the error was raised, even if that -field is not present in the response. +`Non-Null` in the query document, the `null` result will propagate to the next +nullable field. In that case, the `path` for the error should include the full +path to the result field where the error was raised, even if that field is not +present in the response. For example, if the `name` field from above had declared a `Non-Null` return type in the schema, the result would look different but the error reported would From 159d15946bee00c9c4bc2c01016a7b5f77cf47bb Mon Sep 17 00:00:00 2001 From: twof Date: Fri, 13 May 2022 13:45:18 -0700 Subject: [PATCH 21/21] updated to reflect newest behavior --- spec/Appendix B -- Grammar Summary.md | 11 ++++- spec/Section 2 -- Language.md | 61 +++++++++++++++++---------- spec/Section 5 -- Validation.md | 12 +++--- spec/Section 6 -- Execution.md | 57 +++++++++++++++++-------- spec/Section 7 -- Response.md | 9 ++-- 5 files changed, 98 insertions(+), 52 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index 0f849271b..dee184467 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -162,8 +162,15 @@ Alias : Name : Nullability : -- ! -- ? +- ListNullability NullabilityDesignator? +- NullabilityDesignator + +ListNullability : `[` Nullability? `]` + +NullabilityDesignator : + +- `!` +- `?` Arguments[Const] : ( Argument[?Const]+ ) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index d9ce6653e..695ff13e8 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -519,12 +519,12 @@ which returns the result: Nullability : -- ListNullability NullabilityModifier? -- NullabilityModifier +- ListNullability NullabilityDesignator? +- NullabilityDesignator ListNullability : `[` Nullability? `]` -NullabilityModifier : +NullabilityDesignator : - `!` - `?` @@ -534,13 +534,14 @@ a field should be `Non-Nullable` or a `?` to indicate that a field should be `Nullable`. These designators override the nullability set on a field by the schema for the operation where they're being used. In addition to being `Non-Nullable`, if a field marked with `!` resolves to `null`, it propagates to -the nearest parent field marked with a `?` or to `data` if one does not exist. -An error is added to the `errors` array identical to if the field had been -`Non-Nullable` in the schema. +the nearest parent field marked with a `?` or to `data` if there is not a parent +marked with a `?`. An error is added to the `errors` array identical to if the +field had been `Non-Nullable` in the schema. In this example, we can indicate that a `user`'s `name` that could possibly be `null`, should not be `null` and that `null` propagation should halt at the -`user` field: +`user` field. We can use `?` to create null propagation boundary. `user` will be +treated as `Nullable` for this operation: ```graphql example { @@ -551,8 +552,8 @@ In this example, we can indicate that a `user`'s `name` that could possibly be } ``` -If `name` comes back non-`null`, then the return value is the same as if the -nullability designator was not used: +If `name` is resolved to a value other than `null`, then the return value is the +same as if the designators were not used: ```json example { @@ -563,9 +564,9 @@ nullability designator was not used: } ``` -In the event that `name` is `null`, the field's parent selection set becomes -`null` in the result and an error is returned, just as if `name` was marked -`Non-Nullable` in the schema: +In the event that `name` resolves to `null`, the field's parent selection set +becomes `null` in the result and an error is returned, just as if `name` was +marked `Non-Nullable` in the schema: ```json example { @@ -582,19 +583,33 @@ In the event that `name` is `null`, the field's parent selection set becomes } ``` -If `user` was `Non-Nullable` in the schema, but we don't want `null`s -propagating past that point, then we can use `?` to create null propagation -boundary. `User` will be treated as `Nullable` for this operation: +If `!` is used on a field and it is not paired with `?` on a parent, then `null` +will propagate all the way to the `data` response field. ```graphql example { - user(id: 4)? { + user(id: 4) { id name! } } ``` +Response: + +```json example +{ + "data": null, + "errors": [ + { + "locations": [{ "column": 13, "line": 4 }], + "message": "Cannot return null for non-nullable field User.name.", + "path": ["user", "name"] + } + ] +} +``` + Nullability designators can also be applied to list elements like so. ```graphql example @@ -616,13 +631,13 @@ syntax can be applied to multidimensional lists. } ``` -Any element without a nullability designator will inherit its nullability from -the schema definition, exactly the same as non-list fields do. When designating -nullability for list fields, query authors can either use a single designator -(`!` or `?`) to designate the nullability of the entire field, or they can use -the list element nullability syntax displayed above. The number of dimensions -indicated by list element nullability syntax is required to match the number of -dimensions of the field. Anything else results in a query validation error. +Any field without a nullability designator will inherit its nullability from the +schema definition. When designating nullability for list fields, query authors +can either use a single designator (`!` or `?`) to designate the nullability of +the entire field, or they can use the list element nullability syntax displayed +above. The number of dimensions indicated by list element nullability syntax is +required to match the number of dimensions of the field. Anything else results +in a query validation error. ## Fragments diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 77d89363f..3e862925f 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -565,16 +565,16 @@ fragment conflictingDifferingResponses on Pet { ``` The same is true if a field is designated `Non-Nullable` in an operation. In -this case, `someValue` could be either a `String` or a `String!` which are two +this case, `nickname` could be either a `String` or a `String!` which are two different types and therefore can not be merged: ```graphql counter-example fragment conflictingDifferingResponses on Pet { ... on Dog { - someValue: nickname + nickname } ... on Cat { - someValue: nickname! + nickname! } } ``` @@ -587,8 +587,10 @@ fragment conflictingDifferingResponses on Pet { - Let {fieldDef} be the definition of {field} - Let {fieldType} be the type of {fieldDef} - Let {requiredStatus} be the required status of {field} - - Let {designatorDepth} be the number of square bracket pairs in + - Let {designatorDepth} be the number of ListNullability operators in {requiredStatus} + - If {designatorDepth} is 0 + - return true - Let {typeDepth} be the number of list dimensions in {fieldType} - If {typeDepth} equals {designatorDepth} or {designatorDepth} equals 0 return true @@ -599,7 +601,7 @@ fragment conflictingDifferingResponses on Pet { List fields can be marked with nullability designators that look like `[?]!` to indicate the nullability of the list's elements and the nullability of the list itself. For multi-dimensional lists, the designator would look something like -`[[[!]?]]!`. If the designator is not a simple `!` or `?`, then the number of +`[[[!]?]]!`. If any `ListNullability` operators are used then the number of dimensions of the designator are required to match the number of dimensions of the field's type. If the two do not match then a validation error is thrown. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index e601254ae..e859da18f 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -564,30 +564,49 @@ Each field requested in the grouped field set that is defined on the selected objectType will result in an entry in the response map. Field execution first coerces any provided argument values, then resolves a value for the field, and finally completes that value either by recursively executing another selection -set or coercing a scalar value. +set or coercing a scalar value. `ccnPropagationPairs` is an unordered map where +the keys are paths of required fields, and values are paths of the nearest +optional parent to those required fields. `currentPropagationPath` starts as an +empty path to indicate that `null` propagation should continue until it hits +`data` if there is no optional field. -ExecuteField(objectType, objectValue, fieldType, fields, variableValues): +ExecuteField(objectType, objectValue, fieldType, fields, variableValues, +currentPropagationPath, ccnPropagationPairs): - Let {field} be the first entry in {fields}. - Let {fieldName} be the field name of {field}. - Let {requiredStatus} be the required status of {field}. +- Let {newPropagationPath} be {path} if {requiredStatus} is optional, otherwise + let {newPropagationPath} be {currentPropagationPath} +- If {requiredStatus} is optional: + - Let {newPropagationPath} be {path} +- If {requiredStatus} is required: + - Set {path} to {newPropagationPath} in {ccnPropagationPairs} - Let {argumentValues} be the result of {CoerceArgumentValues(objectType, field, variableValues)} - Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName, argumentValues)}. -- Let {modifiedFieldType} be {ModifiedOutputType(fieldType, requiredStatus)}. +- Let {modifiedFieldType} be {ApplyRequiredStatus(fieldType, requiredStatus)}. - Return the result of {CompleteValue(modifiedFieldType, fields, resolvedValue, - variableValues)}. + variableValues, newPropagationPath, ccnPropagationPairs)}. ## Accounting For Client Controlled Nullability Designators A field can have its nullability status set either in its service's schema, or a -nullability designator (! or ?) can override it for the duration of an +nullability designator (`!` or `?`) can override it for the duration of an execution. In order to determine a field's true nullability, both are taken into -account and a final type is produced. - -ModifiedOutputType(outputType, requiredStatus): - +account and a final type is produced. A field marked with a `!` is called a +"required field" and a field marked with a `?` is called an optional field. + +ApplyRequiredStatus(type, requiredStatus): + +- If there is no {requiredStatus}: + - return {type} +- If {requiredStatus} is not a list: + - If {requiredStatus} is required: + - return a `Non-Null` version of {type} + - If {requiredStatus} is optional: + - return a nullable version of {type} - Create a {stack} initially containing {type}. - As long as the top of {stack} is a list: - Let {currentType} be the top item of {stack}. @@ -616,8 +635,8 @@ ModifiedOutputType(outputType, requiredStatus): - If the nullable type of {listType} is not a list - Pop the top of {stack} and set {listType} to the result - If {listType} does not exist: - - Throw an error because {requiredStatus} had more list dimensions than - {outputType} and is invalid. + - Raise a field error because {requiredStatus} had more list dimensions + than {outputType} and is invalid. - If {resultingType} exist: - If {listType} is Non-Nullable: - Set {resultingType} to a Non-Nullable list where the element is @@ -627,11 +646,9 @@ ModifiedOutputType(outputType, requiredStatus): - Continue onto the next node. - Set {resultingType} to {listType} - If {stack} is not empty: - - Throw an error because {requiredStatus} had fewer list dimensions than + - Raise a field error because {requiredStatus} had fewer list dimensions than {outputType} and is invalid. - Return {resultingType}. -- Otherwise: - - Return {outputType}. ### Coercing Field Arguments @@ -835,8 +852,9 @@ response. If the result of resolving a field is {null} (either because the function to resolve the field returned {null} or because a field error was raised), and the -{ModifiedOutputType} of that field is of a `Non-Null` type, then a field error -is raised. The error must be added to the {"errors"} list in the response. +type of the field after {ApplyRequiredStatus} has been applied to it is of a +`Non-Null` type, then a field error is raised. The error must be added to the +{"errors"} list in the response. If the field returns {null} because of a field error which has already been added to the {"errors"} list in the response, the {"errors"} list must not be @@ -845,8 +863,11 @@ field. Since `Non-Null` type fields cannot be {null}, 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 its {ModifiedOutputType} is a `Non-Null` type, the field -error is further propagated to its parent field. +to {null}, otherwise if it is a `Non-Null` type, the field error is further +propagated to its parent field. + +If a required field resolves to {null}, propagation instead happens until an +optional field is found. 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` diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index d5f308629..f560aee3f 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -160,10 +160,11 @@ The response might look like: } ``` -If the field which experienced an error was declared as `Non-Null` or designated -`Non-Null` in the query document, the `null` result will propagate to the next -nullable field. In that case, the `path` for the error should include the full -path to the result field where the error was raised, even if that field is not +If the field which experienced an error was declared as `Non-Null`, the `null` +result will propagate to the next nullable field. If it was marked with a +required designator, then it will propagate to the nearest optional parent field +instead. In either case, the `path` for the error should include the full path +to the result field where the error was raised, even if that field is not present in the response. For example, if the `name` field from above had declared a `Non-Null` return