-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Fix ambiguity with null variable values and default values #418
Changes from 2 commits
ef26891
3dc6d1b
2fbeb5d
ee6220b
429bf2b
3147689
03197f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -710,25 +710,26 @@ and invalid. | |
* {arguments} must be the set containing only {argument}. | ||
|
||
|
||
#### Required Non-Null Arguments | ||
#### Required Arguments | ||
|
||
* For each Field or Directive in the document. | ||
* Let {arguments} be the arguments provided by the Field or Directive. | ||
* Let {argumentDefinitions} be the set of argument definitions of that Field or Directive. | ||
* For each {definition} in {argumentDefinitions}: | ||
* Let {type} be the expected type of {definition}. | ||
* If {type} is Non-Null: | ||
* Let {argumentName} be the name of {definition}. | ||
* For each {argumentDefinition} in {argumentDefinitions}: | ||
* Let {type} be the expected type of {argumentDefinition}. | ||
* Let {defaultValue} be the default value of {argumentDefinition}. | ||
* If {type} is Non-Null and {defaultValue} does not exist: | ||
* Let {argumentName} be the name of {argumentDefinition}. | ||
* Let {argument} be the argument in {arguments} named {argumentName} | ||
* {argument} must exist. | ||
* Let {value} be the value of {argument}. | ||
* {value} must not be the {null} literal. | ||
|
||
**Explanatory Text** | ||
|
||
Arguments can be required. If the argument type is non-null the argument is | ||
required and furthermore the explicit value {null} may not be provided. | ||
Otherwise, the argument is optional. | ||
Arguments can be required. If the argument type is non-null and does not have a | ||
default value, the argument is required and furthermore the explicit value | ||
{null} may not be provided. Otherwise, the argument is optional. | ||
|
||
For example the following are valid: | ||
|
||
|
@@ -738,7 +739,7 @@ fragment goodBooleanArg on Arguments { | |
} | ||
|
||
fragment goodNonNullArg on Arguments { | ||
nonNullBooleanArgField(nonNullBooleanArg: true) | ||
requiredBooleanArgField(requiredBooleanArg: true) | ||
} | ||
``` | ||
|
||
|
@@ -752,19 +753,19 @@ fragment goodBooleanArgDefault on Arguments { | |
} | ||
``` | ||
|
||
but this is not valid on a non-null argument. | ||
but this is not valid on a required argument. | ||
|
||
```graphql counter-example | ||
fragment missingRequiredArg on Arguments { | ||
nonNullBooleanArgField | ||
requiredBooleanArgField | ||
} | ||
``` | ||
|
||
Providing the explicit value {null} is also not valid. | ||
|
||
```graphql counter-example | ||
fragment missingRequiredArg on Arguments { | ||
notNullBooleanArgField(nonNullBooleanArg: null) | ||
requiredBooleanArgField(requiredBooleanArg: null) | ||
} | ||
``` | ||
|
||
|
@@ -1358,6 +1359,31 @@ For example the following query will not pass validation. | |
``` | ||
|
||
|
||
### Input Object Required Fields | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This rule exists as part of GraphQL.js's |
||
|
||
**Formal Specification** | ||
|
||
* For each Input Object in the document. | ||
* Let {fields} be the fields provided by that Input Object. | ||
* Let {fieldDefinitions} be the set of input field definitions of that Input Object. | ||
* For each {fieldDefinition} in {fieldDefinitions}: | ||
* Let {type} be the expected type of {fieldDefinition}. | ||
* Let {defaultValue} be the default value of {fieldDefinition}. | ||
* If {type} is Non-Null and {defaultValue} does not exist: | ||
* Let {fieldName} be the name of {fieldDefinition}. | ||
* Let {field} be the input field in {fields} named {fieldName} | ||
* {field} must exist. | ||
* Let {value} be the value of {field}. | ||
* {value} must not be the {null} literal. | ||
|
||
**Explanatory Text** | ||
|
||
Input object fields can be required. If the input object field type is non-null | ||
and does not have a default value, the input object field is required and | ||
furthermore the explicit value {null} may not be provided. Otherwise, the input | ||
object field is optional. | ||
|
||
|
||
## Directives | ||
|
||
|
||
|
@@ -1494,44 +1520,6 @@ fragment HouseTrainedFragment { | |
``` | ||
|
||
|
||
### Variable Default Value Is Allowed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This rule is completely removed since the queries it prohibited we explicitly wish to support. |
||
|
||
**Formal Specification** | ||
|
||
* For every Variable Definition {variableDefinition} in a document | ||
* Let {variableType} be the type of {variableDefinition} | ||
* Let {defaultValue} be the default value of {variableDefinition} | ||
* If {variableType} is Non-null: | ||
* {defaultValue} must be undefined. | ||
|
||
**Explanatory Text** | ||
|
||
Variables defined by operations are allowed to define default values | ||
if the type of that variable is not non-null. | ||
|
||
For example the following query will pass validation. | ||
|
||
```graphql example | ||
query houseTrainedQuery($atOtherHomes: Boolean = true) { | ||
dog { | ||
isHousetrained(atOtherHomes: $atOtherHomes) | ||
} | ||
} | ||
``` | ||
|
||
However if the variable is defined as non-null, default values | ||
are unreachable. Therefore queries such as the following fail | ||
validation | ||
|
||
```graphql counter-example | ||
query houseTrainedQuery($atOtherHomes: Boolean! = true) { | ||
dog { | ||
isHousetrained(atOtherHomes: $atOtherHomes) | ||
} | ||
} | ||
``` | ||
|
||
|
||
### Variables Are Input Types | ||
|
||
**Formal Specification** | ||
|
@@ -1836,17 +1824,33 @@ an extraneous variable. | |
* For each {operation} in {document} | ||
* Let {variableUsages} be all usages transitively included in the {operation} | ||
* For each {variableUsage} in {variableUsages} | ||
* Let {variableType} be the type of variable definition in the {operation} | ||
* Let {argumentType} be the type of the argument the variable is passed to. | ||
* Let {hasDefault} be true if the variable definition defines a default. | ||
* AreTypesCompatible({argumentType}, {variableType}, {hasDefault}) must be true | ||
|
||
* AreTypesCompatible({argumentType}, {variableType}, {hasDefault}): | ||
* If {hasDefault} is true, treat the {variableType} as non-null. | ||
* If inner type of {argumentType} and {variableType} are different, return false | ||
* If {argumentType} and {variableType} have different list dimensions, return false | ||
* If any list level of {variableType} is not non-null, and the corresponding level | ||
in {argument} is non-null, the types are not compatible. | ||
* Let {variableName} be the name of {variableUsage} | ||
* Let {variableDefinition} be the {VariableDefinition} in {operation}. | ||
* Let {variableType} be the type of {variableDefinition}. | ||
* Let {defaultValue} be the default value of {variableDefinition}. | ||
* If {variableType} is not a non-null and {defaultValue} is provided and not {null}: | ||
* Let {variableType} be the non-null of {variableType}. | ||
* Let {argumentType} be the type of the argument {variableUsage} is passed to. | ||
* AreTypesCompatible({argumentType}, {variableType}) must be {true}. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Most of this change is for clarity, however the default value is now checked for {null} first |
||
|
||
AreTypesCompatible(argumentType, variableType): | ||
* If {argumentType} is a non-null type: | ||
* If {variableType} is a non-null type: | ||
* Let {nullableArgumentType} be the nullable type of {argumentType}. | ||
* Let {nullableVariableType} be the nullable type of {variableType}. | ||
* Return {AreTypesCompatible(nullableArgumentType, nullableVariableType)}. | ||
* Otherwise return {false}. | ||
* If {variableType} is a non-null type: | ||
* Let {nullableVariableType} be the nullable type of {variableType}. | ||
* Return {AreTypesCompatible(argumentType, nullableVariableType)}. | ||
* If {argumentType} is a list type: | ||
* If {variableType} is a list type: | ||
* Let {itemArgumentType} be the item type of {argumentType}. | ||
* Let {itemVariableType} be the item type of {variableType}. | ||
* Return {AreTypesCompatible(itemArgumentType, itemVariableType)}. | ||
* Otherwise return {false}. | ||
* If {variableType} is a list type return {false}. | ||
* Return if {variableType} and {argumentType} are identical. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other than the checking default values being removed from this algorithm, this is all for clarity and doesn't signify any other behavioral change |
||
|
||
**Explanatory Text** | ||
|
||
|
@@ -1890,8 +1894,12 @@ query booleanArgQuery($booleanArg: Boolean) { | |
} | ||
``` | ||
|
||
A notable exception is when default arguments are provided. They are, in effect, | ||
treated as non-nulls. | ||
A notable exception is when a default value is provided. Variables which include | ||
a default value may be provided to a non-null argument as long as the default | ||
value is not {null}. | ||
|
||
Note: The value {null} could still be provided to a variable at runtime, and | ||
a non-null argument must produce a field error if provided such a variable. | ||
|
||
```graphql example | ||
query booleanArgQueryWithDefault($booleanArg: Boolean = true) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,20 +82,28 @@ CoerceVariableValues(schema, operation, variableValues): | |
* For each {variableDefinition} in {variableDefinitions}: | ||
* Let {variableName} be the name of {variableDefinition}. | ||
* Let {variableType} be the expected type of {variableDefinition}. | ||
* Assert: {variableType} must be an input type. | ||
* Let {defaultValue} be the default value for {variableDefinition}. | ||
* Let {value} be the value provided in {variableValues} for the name {variableName}. | ||
* If {value} does not exist (was not provided in {variableValues}): | ||
* If {defaultValue} exists (including {null}): | ||
* Let {hasValue} be {true} if {variableValues} provides a value for the | ||
name {variableName}. | ||
* Let {value} be the value provided in {variableValues} for the | ||
name {variableName}. | ||
* If {hasValue} is not {true} and {defaultValue} exists (including {null}): | ||
* Add an entry to {coercedValues} named {variableName} with the | ||
value {defaultValue}. | ||
* Otherwise if {variableType} is a Non-Nullable type, and either {hasValue} | ||
is not {true} or {value} is {null}, throw a query error. | ||
* Otherwise if {hasValue} is true: | ||
* If {value} is {null}: | ||
* Add an entry to {coercedValues} named {variableName} with the | ||
value {defaultValue}. | ||
* Otherwise if {variableType} is a Non-Nullable type, throw a query error. | ||
* Otherwise, continue to the next variable definition. | ||
* Otherwise, if {value} cannot be coerced according to the input coercion | ||
rules of {variableType}, throw a query error. | ||
* Let {coercedValue} be the result of coercing {value} according to the | ||
input coercion rules of {variableType}. | ||
* Add an entry to {coercedValues} named {variableName} with the | ||
value {coercedValue}. | ||
value {null}. | ||
* Otherwise: | ||
* If {value} cannot be coerced according to the input coercion | ||
rules of {variableType}, throw a query error. | ||
* Let {coercedValue} be the result of coercing {value} according to the | ||
input coercion rules of {variableType}. | ||
* Add an entry to {coercedValues} named {variableName} with the | ||
value {coercedValue}. | ||
* Return {coercedValues}. | ||
|
||
Note: This algorithm is very similar to {CoerceArgumentValues()}. | ||
|
@@ -530,8 +538,8 @@ order to correctly produce a value. These arguments are defined by the field in | |
the type system to have a specific input type: Scalars, Enum, Input Object, or | ||
List or Non-Null wrapped variations of these three. | ||
|
||
At each argument position in a query may be a literal value or a variable to be | ||
provided at runtime. | ||
At each argument position in a query may be a literal {Value} or {Variable} to | ||
be provided at runtime. | ||
|
||
CoerceArgumentValues(objectType, field, variableValues): | ||
* Let {coercedValues} be an empty unordered Map. | ||
|
@@ -543,30 +551,37 @@ CoerceArgumentValues(objectType, field, variableValues): | |
* Let {argumentName} be the name of {argumentDefinition}. | ||
* Let {argumentType} be the expected type of {argumentDefinition}. | ||
* Let {defaultValue} be the default value for {argumentDefinition}. | ||
* Let {value} be the value provided in {argumentValues} for the name {argumentName}. | ||
* If {value} is a Variable: | ||
* Let {variableName} be the name of Variable {value}. | ||
* Let {variableValue} be the value provided in {variableValues} for the name {variableName}. | ||
* If {variableValue} exists (including {null}): | ||
* Add an entry to {coercedValues} named {argName} with the | ||
value {variableValue}. | ||
* Otherwise, if {defaultValue} exists (including {null}): | ||
* Add an entry to {coercedValues} named {argName} with the | ||
value {defaultValue}. | ||
* Otherwise, if {argumentType} is a Non-Nullable type, throw a field error. | ||
* Otherwise, continue to the next argument definition. | ||
* Otherwise, if {value} does not exist (was not provided in {argumentValues}: | ||
* If {defaultValue} exists (including {null}): | ||
* Add an entry to {coercedValues} named {argName} with the | ||
value {defaultValue}. | ||
* Otherwise, if {argumentType} is a Non-Nullable type, throw a field error. | ||
* Otherwise, continue to the next argument definition. | ||
* Otherwise, if {value} cannot be coerced according to the input coercion | ||
rules of {argType}, throw a field error. | ||
* Let {coercedValue} be the result of coercing {value} according to the | ||
input coercion rules of {argType}. | ||
* Add an entry to {coercedValues} named {argName} with the | ||
value {coercedValue}. | ||
* Let {hasValue} be {true} if {argumentValues} provides a value for the | ||
name {argumentName}. | ||
* Let {argumentValue} be the value provided in {argumentValues} for the | ||
name {argumentName}. | ||
* If {argumentValue} is a {Variable}: | ||
* Let {variableName} be the name of {argumentValue}. | ||
* Let {hasValue} be {true} if {variableValues} provides a value for the | ||
name {variableName}. | ||
* Let {value} be the value provided in {variableValues} for the | ||
name {variableName}. | ||
* Otherwise: | ||
* Let {value} be {argumentValue}. | ||
* If {hasValue} is not {true} and {defaultValue} exists (including {null}): | ||
* Add an entry to {coercedValues} named {argumentName} with the | ||
value {defaultValue}. | ||
* Otherwise if {argumentType} is a Non-Nullable type, and either {hasValue} | ||
is not {true} or {value} is {null}, throw a field error. | ||
* Otherwise if {hasValue} is true: | ||
* If {value} is {null}: | ||
* Add an entry to {coercedValues} named {argumentName} with the | ||
value {null}. | ||
* Otherwise, if {argumentValue} is a {Variable}: | ||
* Add an entry to {coercedValues} named {argumentName} with the | ||
value {value}. | ||
* Otherwise: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You probably don't want this "otherwise" here, since coercion and resulting failures need to happen There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is intentional. Variables are coerced during There's an existing note below this algorithm capturing this point There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see – I missed that note below. 👍 |
||
* If {value} cannot be coerced according to the input coercion | ||
rules of {variableType}, throw a field error. | ||
* Let {coercedValue} be the result of coercing {value} according to the | ||
input coercion rules of {variableType}. | ||
* Add an entry to {coercedValues} named {argumentName} with the | ||
value {coercedValue}. | ||
* Return {coercedValues}. | ||
|
||
Note: Variable values are not coerced because they are expected to be coerced | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read this to imply (probably unintentionally) that a non-null argument with a default value is allowed to be passed an explicit
null
?