Skip to content

Commit ef26891

Browse files
committed
RFC: Fix ambiguity with null variable values and default values
> This is a **behavioral change** which changes how explicit `null` values interact with variable and argument default values. This also changes a validation rule which makes the rule more strict. There is currently ambiguity and inconsistency in how `null` values are coerced and resolved as part of variable values, default values, and argument values. This inconsistency and ambiguity can allow for `null` values to appear at non-null arguments, which might result in unforseen null-pointer-errors. This appears in three distinct but related issues: **Validation: All Variable Usages are Allowed** The explicit value `null` may be used as a default value for a variable with a nullable type, however this rule asks to treat a variable's type as non-null if it has a default value. Instead this rule should specifically only treat the variable's type as non-null if the default value is not `null`. Additionally, the `AreTypesCompatible` algorithm is underspecificied, which could lead to further misinterpretation of this validation rule. **Coercing Variable Values** `CoerceVariableValues()` allows the explicit `null` value to be used instead of a default value. This can result in a null value flowing to a non-null argument due to the validation rule mentioned above. Instead a default value must be used even when an explicit `null` value is provided. This is also more consistent with the explanation for validation rule "Variable Default Value Is Allowed" Also, how to treat an explicit `null` value is currently underspecified. While an input object explains that a `null` value should result in an explicit `null` value at the input object field, there is no similar explaination for typical scalar input types. Instead, `CoerceVariableValues()` should explicitly handle the `null` value to make it clear a `null` is the resulting value in the `coercedValues` Map. **Coercing Argument Values** The `CoerceArgumentValues()` algorithm is intentionally similar to `CoerceVariableValues()` and suffers from the same inconsistency. Explicit `null` values should not take precedence over default values, and should also be explicitly handled rather than left to underspecified input scalar coercion.
1 parent 54df48d commit ef26891

File tree

2 files changed

+75
-46
lines changed

2 files changed

+75
-46
lines changed

spec/Section 5 -- Validation.md

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,17 +1836,33 @@ an extraneous variable.
18361836
* For each {operation} in {document}
18371837
* Let {variableUsages} be all usages transitively included in the {operation}
18381838
* For each {variableUsage} in {variableUsages}
1839-
* Let {variableType} be the type of variable definition in the {operation}
1840-
* Let {argumentType} be the type of the argument the variable is passed to.
1841-
* Let {hasDefault} be true if the variable definition defines a default.
1842-
* AreTypesCompatible({argumentType}, {variableType}, {hasDefault}) must be true
1843-
1844-
* AreTypesCompatible({argumentType}, {variableType}, {hasDefault}):
1845-
* If {hasDefault} is true, treat the {variableType} as non-null.
1846-
* If inner type of {argumentType} and {variableType} are different, return false
1847-
* If {argumentType} and {variableType} have different list dimensions, return false
1848-
* If any list level of {variableType} is not non-null, and the corresponding level
1849-
in {argument} is non-null, the types are not compatible.
1839+
* Let {variableName} be the name of {variableUsage}
1840+
* Let {variableDefinition} be the {VariableDefinition} in {operation}.
1841+
* Let {variableType} be the type of {variableDefinition}.
1842+
* Let {defaultValue} be the default value of {variableDefinition}.
1843+
* If {variableType} is not a non-null and {defaultValue} is provided and not {null}:
1844+
* Let {variableType} be the non-null of {variableType}.
1845+
* Let {argumentType} be the type of the argument {variableUsage} is passed to.
1846+
* AreTypesCompatible({argumentType}, {variableType}) must be {true}.
1847+
1848+
AreTypesCompatible(argumentType, variableType):
1849+
* If {argumentType} is a non-null type:
1850+
* If {variableType} is a non-null type:
1851+
* Let {nullableArgumentType} be the nullable type of {argumentType}.
1852+
* Let {nullableVariableType} be the nullable type of {variableType}.
1853+
* Return {AreTypesCompatible(nullableArgumentType, nullableVariableType)}.
1854+
* Otherwise return {false}.
1855+
* If {variableType} is a non-null type:
1856+
* Let {nullableVariableType} be the nullable type of {variableType}.
1857+
* Return {AreTypesCompatible(argumentType, nullableVariableType)}.
1858+
* If {argumentType} is a list type:
1859+
* If {variableType} is a list type:
1860+
* Let {itemArgumentType} be the item type of {argumentType}.
1861+
* Let {itemVariableType} be the item type of {variableType}.
1862+
* Return {AreTypesCompatible(itemArgumentType, itemVariableType)}.
1863+
* Otherwise return {false}.
1864+
* If {variableType} is a list type return {false}.
1865+
* Return if {variableType} and {argumentType} are identical.
18501866

18511867
**Explanatory Text**
18521868

@@ -1890,8 +1906,8 @@ query booleanArgQuery($booleanArg: Boolean) {
18901906
}
18911907
```
18921908

1893-
A notable exception is when default arguments are provided. They are, in effect,
1894-
treated as non-nulls.
1909+
A notable exception is when a default value are provided. They are, in effect,
1910+
treated as non-nulls as long as the default value is not {null}.
18951911

18961912
```graphql example
18971913
query booleanArgQueryWithDefault($booleanArg: Boolean = true) {

spec/Section 6 -- Execution.md

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,27 @@ CoerceVariableValues(schema, operation, variableValues):
8282
* For each {variableDefinition} in {variableDefinitions}:
8383
* Let {variableName} be the name of {variableDefinition}.
8484
* Let {variableType} be the expected type of {variableDefinition}.
85+
* Assert: {variableType} must be an input type.
8586
* Let {defaultValue} be the default value for {variableDefinition}.
86-
* Let {value} be the value provided in {variableValues} for the name {variableName}.
87-
* If {value} does not exist (was not provided in {variableValues}):
88-
* If {defaultValue} exists (including {null}):
87+
* Let {hasValue} be {true} if {variableValues} provides a value for the
88+
name {variableName}.
89+
* Let {value} be the value provided in {variableValues} for the
90+
name {variableName}.
91+
* If {hasValue} is not {true} or if {value} is {null}:
92+
* If {variableType} is a Non-Nullable type, throw a query error.
93+
* Otherwise if {defaultValue} exists (including {null}):
8994
* Add an entry to {coercedValues} named {variableName} with the
9095
value {defaultValue}.
91-
* Otherwise if {variableType} is a Non-Nullable type, throw a query error.
92-
* Otherwise, continue to the next variable definition.
93-
* Otherwise, if {value} cannot be coerced according to the input coercion
94-
rules of {variableType}, throw a query error.
95-
* Let {coercedValue} be the result of coercing {value} according to the
96-
input coercion rules of {variableType}.
97-
* Add an entry to {coercedValues} named {variableName} with the
98-
value {coercedValue}.
96+
* Otherwise if {hasValue} is {true} and {value} is {null}:
97+
* Add an entry to {coercedValues} named {variableName} with the
98+
value {null}.
99+
* Otherwise, {value} must not be {null}:
100+
* If {value} cannot be coerced according to the input coercion
101+
rules of {variableType}, throw a query error.
102+
* Let {coercedValue} be the result of coercing {value} according to the
103+
input coercion rules of {variableType}.
104+
* Add an entry to {coercedValues} named {variableName} with the
105+
value {coercedValue}.
99106
* Return {coercedValues}.
100107

101108
Note: This algorithm is very similar to {CoerceArgumentValues()}.
@@ -530,8 +537,8 @@ order to correctly produce a value. These arguments are defined by the field in
530537
the type system to have a specific input type: Scalars, Enum, Input Object, or
531538
List or Non-Null wrapped variations of these three.
532539

533-
At each argument position in a query may be a literal value or a variable to be
534-
provided at runtime.
540+
At each argument position in a query may be a literal {Value} or {Variable} to
541+
be provided at runtime.
535542

536543
CoerceArgumentValues(objectType, field, variableValues):
537544
* Let {coercedValues} be an empty unordered Map.
@@ -543,30 +550,36 @@ CoerceArgumentValues(objectType, field, variableValues):
543550
* Let {argumentName} be the name of {argumentDefinition}.
544551
* Let {argumentType} be the expected type of {argumentDefinition}.
545552
* Let {defaultValue} be the default value for {argumentDefinition}.
546-
* Let {value} be the value provided in {argumentValues} for the name {argumentName}.
547-
* If {value} is a Variable:
548-
* Let {variableName} be the name of Variable {value}.
549-
* Let {variableValue} be the value provided in {variableValues} for the name {variableName}.
550-
* If {variableValue} exists (including {null}):
551-
* Add an entry to {coercedValues} named {argName} with the
552-
value {variableValue}.
553+
* Let {hasValue} be {true} if {argumentValues} provides a value for the
554+
name {argumentName}.
555+
* Let {value} be the value provided in {argumentValues} for the
556+
name {argumentName}.
557+
* If {hasValue} is not {true} or if {value} is {null}:
558+
* If {argumentType} is a Non-Nullable type, throw a field error.
553559
* Otherwise, if {defaultValue} exists (including {null}):
554-
* Add an entry to {coercedValues} named {argName} with the
560+
* Add an entry to {coercedValues} named {argumentName} with the
555561
value {defaultValue}.
562+
* Otherwise, if {hasValue} is {true} and {value} is {null}:
563+
* Add an entry to {coercedValues} named {argumentName} with the
564+
value {null}.
565+
* Otherwise, if {value} is a {Variable}:
566+
* Let {variableName} be the name of {value}.
567+
* If {variableValues} provides a value for the name {variableName}:
568+
* Let {variableValue} be the value provided in {variableValues} for the
569+
name {variableName}.
570+
* Add an entry to {coercedValues} named {argumentName} with the
571+
value {variableValue}.
556572
* Otherwise, if {argumentType} is a Non-Nullable type, throw a field error.
557-
* Otherwise, continue to the next argument definition.
558-
* Otherwise, if {value} does not exist (was not provided in {argumentValues}:
559-
* If {defaultValue} exists (including {null}):
560-
* Add an entry to {coercedValues} named {argName} with the
573+
* Otherwise, if {defaultValue} exists (including {null}):
574+
* Add an entry to {coercedValues} named {argumentName} with the
561575
value {defaultValue}.
562-
* Otherwise, if {argumentType} is a Non-Nullable type, throw a field error.
563-
* Otherwise, continue to the next argument definition.
564-
* Otherwise, if {value} cannot be coerced according to the input coercion
565-
rules of {argType}, throw a field error.
566-
* Let {coercedValue} be the result of coercing {value} according to the
567-
input coercion rules of {argType}.
568-
* Add an entry to {coercedValues} named {argName} with the
569-
value {coercedValue}.
576+
* Otherwise, {value} must be a {Value} and not {null}:
577+
* If {value} cannot be coerced according to the input coercion
578+
rules of {argType}, throw a field error.
579+
* Let {coercedValue} be the result of coercing {value} according to the
580+
input coercion rules of {argType}.
581+
* Add an entry to {coercedValues} named {argumentName} with the
582+
value {coercedValue}.
570583
* Return {coercedValues}.
571584

572585
Note: Variable values are not coerced because they are expected to be coerced

0 commit comments

Comments
 (0)