From 64c21eb26d971ef83602b2e369a1ef85734cf28d Mon Sep 17 00:00:00 2001 From: Young Min Kim Date: Mon, 14 Aug 2023 05:19:45 +0000 Subject: [PATCH 1/3] Simplify the spec to only include the ! nullability designator --- spec/Appendix B -- Grammar Summary.md | 3 +- spec/Section 2 -- Language.md | 69 +++++++-------------------- spec/Section 5 -- Validation.md | 14 +++--- spec/Section 6 -- Execution.md | 11 ++--- spec/Section 7 -- Response.md | 8 ++-- 5 files changed, 33 insertions(+), 72 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index dee184467..42cfd68b6 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 :: @@ -170,7 +170,6 @@ ListNullability : `[` Nullability? `]` NullabilityDesignator : - `!` -- `?` Arguments[Const] : ( Argument[?Const]+ ) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 695ff13e8..a70829cd3 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 @@ -527,25 +527,19 @@ ListNullability : `[` Nullability? `]` NullabilityDesignator : - `!` -- `?` -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 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. +Fields can have their nullability designated with a `!` to indicate that a field +should be `Non-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 behaves +as 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. We can use `?` to create null propagation boundary. `user` will be -treated as `Nullable` for this operation: +`null`, should not be `null`: ```graphql example { - user(id: 4)? { + user(id: 4) { id name! } @@ -583,61 +577,34 @@ marked `Non-Nullable` in the schema: } ``` -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) { - 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 { - user(id: 4)? { + user(id: 4) { id - petsNames[!]? + 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. +should be `Non-Nullable`. The same syntax can be applied to multidimensional +lists. ```graphql example { - threeDimensionalMatrix[[[?]!]]! + threeDimensionalMatrix[[[]!]]! } ``` 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. +can either use the designator `!` 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 3e862925f..ad05696ec 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -592,18 +592,18 @@ fragment conflictingDifferingResponses on Pet { - 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 - - Otherwise return false + - If {designatorDepth} exceeds {typeDepth} return false + - Otherwise return true **Explanatory Text** -List fields can be marked with nullability designators that look like `[?]!` to +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 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. +`[[[!]]]!`. If any `ListNullability` operators are used then the number of +dimensions of the designator are required be less than the number of dimensions +of the field's type. If the number of dimensions of the designator exceed the +number of dimensions of the field's type, then a validation error is thrown. ### Leaf Field Selections diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index e859da18f..8bab726e5 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -593,10 +593,10 @@ currentPropagationPath, 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 -execution. In order to determine a field's true nullability, both are taken into -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. +nullability designator (`!`) 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 marked with a `!` is called a "required +field". ApplyRequiredStatus(type, requiredStatus): @@ -866,9 +866,6 @@ 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 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` type is also wrapped in a `Non-Null`, the field error continues to propagate diff --git a/spec/Section 7 -- Response.md b/spec/Section 7 -- Response.md index f560aee3f..7a47754fc 100644 --- a/spec/Section 7 -- Response.md +++ b/spec/Section 7 -- Response.md @@ -161,11 +161,9 @@ The response might look like: ``` 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. +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 adc79a8d1ce181eb89303bdff28c6ce109644678 Mon Sep 17 00:00:00 2001 From: Young Min Kim Date: Mon, 14 Aug 2023 05:34:34 +0000 Subject: [PATCH 2/3] Do another pass to remove the ? nullability designattor --- spec/Section 2 -- Language.md | 6 ++---- spec/Section 5 -- Validation.md | 2 +- spec/Section 6 -- Execution.md | 33 ++++----------------------------- 3 files changed, 7 insertions(+), 34 deletions(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index a70829cd3..b0b2889b9 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -530,8 +530,7 @@ NullabilityDesignator : Fields can have their nullability designated with a `!` to indicate that a field should be `Non-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 behaves +field by the schema for the operation where they're being used. If a field marked with `!` resolves to `null`, it behaves as 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 @@ -602,8 +601,7 @@ 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 the designator `!` 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 +number of dimensions indicated by list element nullability syntax cannot exceed 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 ad05696ec..23f656f43 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -601,7 +601,7 @@ 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 any `ListNullability` operators are used then the number of -dimensions of the designator are required be less than the number of dimensions +dimensions of the designator are required to be less than or equal to the number of dimensions of the field's type. If the number of dimensions of the designator exceed the number of dimensions of the field's type, then a validation error is thrown. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 8bab726e5..faf647763 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -564,31 +564,18 @@ 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. `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. +set or coercing a scalar value. -ExecuteField(objectType, objectValue, fieldType, fields, variableValues, -currentPropagationPath, ccnPropagationPairs): +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 {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 {ApplyRequiredStatus(fieldType, requiredStatus)}. -- Return the result of {CompleteValue(modifiedFieldType, fields, resolvedValue, - variableValues, newPropagationPath, ccnPropagationPairs)}. +- Return the result of {CompleteValue(fieldType, fields, resolvedValue, + variableValues)}. ## Accounting For Client Controlled Nullability Designators @@ -605,8 +592,6 @@ ApplyRequiredStatus(type, requiredStatus): - 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}. @@ -623,13 +608,6 @@ ApplyRequiredStatus(type, requiredStatus): - 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 @@ -645,9 +623,6 @@ ApplyRequiredStatus(type, requiredStatus): - Set {resultingType} to a list where the element is {resultingType}. - Continue onto the next node. - Set {resultingType} to {listType} -- If {stack} is not empty: - - Raise a field error because {requiredStatus} had fewer list dimensions than - {outputType} and is invalid. - Return {resultingType}. ### Coercing Field Arguments From 7bc6b0769ddc87ed8d381f0365e7d6daab1de37f Mon Sep 17 00:00:00 2001 From: Young Min Kim Date: Mon, 25 Sep 2023 23:18:58 +0000 Subject: [PATCH 3/3] Update the execution logic per https://github.com/graphql/graphql-js/pull/3943 --- spec/Section 2 -- Language.md | 22 ++++++++++--- spec/Section 5 -- Validation.md | 7 +++-- spec/Section 6 -- Execution.md | 55 +++++++++------------------------ 3 files changed, 36 insertions(+), 48 deletions(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index b0b2889b9..7d2464cd4 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -530,8 +530,9 @@ NullabilityDesignator : Fields can have their nullability designated with a `!` to indicate that a field should be `Non-Nullable`. These designators override the nullability set on a -field by the schema for the operation where they're being used. If a field marked with `!` resolves to `null`, it behaves -as if the field had been `Non-Nullable` in the schema. +field by the schema for the operation where they're being used. If a field +marked with `!` resolves to `null`, it behaves as 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`: @@ -601,8 +602,21 @@ 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 the designator `!` 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 cannot exceed the number of dimensions of the field. Anything else results in a query -validation error. +number of dimensions indicated by list element nullability syntax cannot exceed +the number of dimensions of the field. Anything else results in a query +validation error. Both are valid examples: + +```graphql example +{ + threeDimensionalMatrix[[[!]]] +} +``` + +```graphql example +{ + threeDimensionalMatrix[[[!]!]!]! +} +``` ## Fragments diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 23f656f43..8a6521da1 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -601,9 +601,10 @@ 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 any `ListNullability` operators are used then the number of -dimensions of the designator are required to be less than or equal to the number of dimensions -of the field's type. If the number of dimensions of the designator exceed the -number of dimensions of the field's type, then a validation error is thrown. +dimensions of the designator are required to be less than or equal to the number +of dimensions of the field's type. If the number of dimensions of the designator +exceed the number of dimensions of the field's type, then a validation error is +thrown. ### Leaf Field Selections diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index faf647763..888bce254 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -570,10 +570,13 @@ ExecuteField(objectType, objectValue, fieldType, fields, variableValues): - Let {field} be the first entry in {fields}. - Let {fieldName} be the field name of {field}. +- Let {nullabilityAssertionNode} be the nullability assertion node of + {fieldName}. - Let {argumentValues} be the result of {CoerceArgumentValues(objectType, field, - variableValues)} + variableValues)}. - Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName, argumentValues)}. +- Let {returnType} be {SimpleTypeTransfer(fieldType, nullabilityAssertionNode)}. - Return the result of {CompleteValue(fieldType, fields, resolvedValue, variableValues)}. @@ -585,45 +588,11 @@ In order to determine a field's true nullability, both are taken into account and a final type is produced. A field marked with a `!` is called a "required 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} -- 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 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: - - 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 - {resultingType}. - - Otherwise: - - Set {resultingType} to a list where the element is {resultingType}. - - Continue onto the next node. - - Set {resultingType} to {listType} -- Return {resultingType}. +SimpleTypeTransfer(fieldType, nullabilityAssertionNode): + +- If {nullabilityAssertionNode} is a {Non-Null NullabilityDesignator} + - Return the Non-Nullable type of {fieldType}. +- Return {fieldType}. ### Coercing Field Arguments @@ -723,8 +692,12 @@ CompleteValue(fieldType, fields, result, variableValues): - If {fieldType} is a List type: - If {result} is not a collection of values, raise a field error. - Let {innerType} be the inner type of {fieldType}. + - Let {nullabilityAssertionNode} be the nullability assertion node of + {innerType}. + - Let {returnType} be {SimpleTypeTransfer(innerType, + nullabilityAssertionNode)}. - Return a list where each list item is the result of calling - {CompleteValue(innerType, fields, resultItem, variableValues)}, where + {CompleteValue(returnType, fields, resultItem, variableValues)}, where {resultItem} is each item in {result}. - If {fieldType} is a Scalar or Enum type: - Return the result of {CoerceResult(fieldType, result)}.