Skip to content

Commit d3599a4

Browse files
committed
Updated based on feedback.
This updates this proposal to be a bit broader in scope however much narrower in breaking behavior changes. Mirroring the changes in graphql/graphql-js#1274, this update better defines the difference between a "required" and "non-null" argument / input field as a non-null typed argument / input-field with a default value is no longer required. As such the validation rule which prohibited queries from using non-null variables and default values has been removed. This also adds clarity to the input field validation - this rule has existed in the GraphQL.js reference implementation however was found missing within the spec. This also updates the CoerceVariableValues() and CoerceArgumentValues() algorithms to retain explicit null values overriding a default value (minimizing breaking changes), however critically adding additional protection to CoerceArgumentValues() to explicitly block null values from variables - thus allowing the older pattern of passing a nullable variable into a non-null argument while limiting the problematic case of an explicit null value at runtime.
1 parent ef26891 commit d3599a4

File tree

2 files changed

+76
-86
lines changed

2 files changed

+76
-86
lines changed

spec/Section 5 -- Validation.md

+38-50
Original file line numberDiff line numberDiff line change
@@ -710,25 +710,26 @@ and invalid.
710710
* {arguments} must be the set containing only {argument}.
711711

712712

713-
#### Required Non-Null Arguments
713+
#### Required Arguments
714714

715715
* For each Field or Directive in the document.
716716
* Let {arguments} be the arguments provided by the Field or Directive.
717717
* Let {argumentDefinitions} be the set of argument definitions of that Field or Directive.
718-
* For each {definition} in {argumentDefinitions}:
719-
* Let {type} be the expected type of {definition}.
720-
* If {type} is Non-Null:
721-
* Let {argumentName} be the name of {definition}.
718+
* For each {argumentDefinition} in {argumentDefinitions}:
719+
* Let {type} be the expected type of {argumentDefinition}.
720+
* Let {defaultValue} be the default value of {argumentDefinition}.
721+
* If {type} is Non-Null and {defaultValue} does not exist:
722+
* Let {argumentName} be the name of {argumentDefinition}.
722723
* Let {argument} be the argument in {arguments} named {argumentName}
723724
* {argument} must exist.
724725
* Let {value} be the value of {argument}.
725726
* {value} must not be the {null} literal.
726727

727728
**Explanatory Text**
728729

729-
Arguments can be required. If the argument type is non-null the argument is
730-
required and furthermore the explicit value {null} may not be provided.
731-
Otherwise, the argument is optional.
730+
Arguments can be required. If the argument type is non-null and does not have a
731+
default value, the argument is required and furthermore the explicit value
732+
{null} may not be provided. Otherwise, the argument is optional.
732733

733734
For example the following are valid:
734735

@@ -738,7 +739,7 @@ fragment goodBooleanArg on Arguments {
738739
}
739740

740741
fragment goodNonNullArg on Arguments {
741-
nonNullBooleanArgField(nonNullBooleanArg: true)
742+
requiredBooleanArgField(requiredBooleanArg: true)
742743
}
743744
```
744745

@@ -752,19 +753,19 @@ fragment goodBooleanArgDefault on Arguments {
752753
}
753754
```
754755

755-
but this is not valid on a non-null argument.
756+
but this is not valid on a required argument.
756757

757758
```graphql counter-example
758759
fragment missingRequiredArg on Arguments {
759-
nonNullBooleanArgField
760+
requiredBooleanArgField
760761
}
761762
```
762763

763764
Providing the explicit value {null} is also not valid.
764765

765766
```graphql counter-example
766767
fragment missingRequiredArg on Arguments {
767-
notNullBooleanArgField(nonNullBooleanArg: null)
768+
requiredBooleanArgField(requiredBooleanArg: null)
768769
}
769770
```
770771

@@ -1358,6 +1359,31 @@ For example the following query will not pass validation.
13581359
```
13591360

13601361

1362+
### Input Object Required Fields
1363+
1364+
**Formal Specification**
1365+
1366+
* For each Input Object in the document.
1367+
* Let {fields} be the fields provided by that Input Object.
1368+
* Let {fieldDefinitions} be the set of input field definitions of that Input Object.
1369+
* For each {fieldDefinition} in {fieldDefinitions}:
1370+
* Let {type} be the expected type of {fieldDefinition}.
1371+
* Let {defaultValue} be the default value of {fieldDefinition}.
1372+
* If {type} is Non-Null and {defaultValue} does not exist:
1373+
* Let {fieldName} be the name of {fieldDefinition}.
1374+
* Let {field} be the input field in {fields} named {fieldName}
1375+
* {field} must exist.
1376+
* Let {value} be the value of {field}.
1377+
* {value} must not be the {null} literal.
1378+
1379+
**Explanatory Text**
1380+
1381+
Input object fields can be required. If the input object field type is non-null
1382+
and does not have a default value, the input object field is required and
1383+
furthermore the explicit value {null} may not be provided. Otherwise, the input
1384+
object field is optional.
1385+
1386+
13611387
## Directives
13621388

13631389

@@ -1494,44 +1520,6 @@ fragment HouseTrainedFragment {
14941520
```
14951521

14961522

1497-
### Variable Default Value Is Allowed
1498-
1499-
**Formal Specification**
1500-
1501-
* For every Variable Definition {variableDefinition} in a document
1502-
* Let {variableType} be the type of {variableDefinition}
1503-
* Let {defaultValue} be the default value of {variableDefinition}
1504-
* If {variableType} is Non-null:
1505-
* {defaultValue} must be undefined.
1506-
1507-
**Explanatory Text**
1508-
1509-
Variables defined by operations are allowed to define default values
1510-
if the type of that variable is not non-null.
1511-
1512-
For example the following query will pass validation.
1513-
1514-
```graphql example
1515-
query houseTrainedQuery($atOtherHomes: Boolean = true) {
1516-
dog {
1517-
isHousetrained(atOtherHomes: $atOtherHomes)
1518-
}
1519-
}
1520-
```
1521-
1522-
However if the variable is defined as non-null, default values
1523-
are unreachable. Therefore queries such as the following fail
1524-
validation
1525-
1526-
```graphql counter-example
1527-
query houseTrainedQuery($atOtherHomes: Boolean! = true) {
1528-
dog {
1529-
isHousetrained(atOtherHomes: $atOtherHomes)
1530-
}
1531-
}
1532-
```
1533-
1534-
15351523
### Variables Are Input Types
15361524

15371525
**Formal Specification**

spec/Section 6 -- Execution.md

+38-36
Original file line numberDiff line numberDiff line change
@@ -88,21 +88,22 @@ CoerceVariableValues(schema, operation, variableValues):
8888
name {variableName}.
8989
* Let {value} be the value provided in {variableValues} for the
9090
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}):
94-
* Add an entry to {coercedValues} named {variableName} with the
95-
value {defaultValue}.
96-
* Otherwise if {hasValue} is {true} and {value} is {null}:
91+
* If {hasValue} is not {true} and {defaultValue} exists (including {null}):
92+
* Add an entry to {coercedValues} named {variableName} with the
93+
value {defaultValue}.
94+
* Otherwise if {variableType} is a Non-Nullable type, and either {hasValue}
95+
is not {true} or {value} is {null}, throw a query error.
96+
* Otherwise if {hasValue} is true:
97+
* If {value} is {null}:
9798
* Add an entry to {coercedValues} named {variableName} with the
9899
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}.
100+
* Otherwise:
101+
* If {value} cannot be coerced according to the input coercion
102+
rules of {variableType}, throw a query error.
103+
* Let {coercedValue} be the result of coercing {value} according to the
104+
input coercion rules of {variableType}.
105+
* Add an entry to {coercedValues} named {variableName} with the
106+
value {coercedValue}.
106107
* Return {coercedValues}.
107108

108109
Note: This algorithm is very similar to {CoerceArgumentValues()}.
@@ -552,34 +553,35 @@ CoerceArgumentValues(objectType, field, variableValues):
552553
* Let {defaultValue} be the default value for {argumentDefinition}.
553554
* Let {hasValue} be {true} if {argumentValues} provides a value for the
554555
name {argumentName}.
555-
* Let {value} be the value provided in {argumentValues} for the
556+
* Let {argumentValue} be the value provided in {argumentValues} for the
556557
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.
559-
* Otherwise, if {defaultValue} exists (including {null}):
560-
* Add an entry to {coercedValues} named {argumentName} with the
561-
value {defaultValue}.
562-
* Otherwise, if {hasValue} is {true} and {value} is {null}:
558+
* If {argumentValue} is a {Variable}:
559+
* Let {variableName} be the name of {argumentValue}.
560+
* Let {hasValue} be {true} if {variableValues} provides a value for the
561+
name {variableName}.
562+
* Let {value} be the value provided in {variableValues} for the
563+
name {variableName}.
564+
* Otherwise:
565+
* Let {value} be {argumentValue}.
566+
* If {hasValue} is not {true} and {defaultValue} exists (including {null}):
567+
* Add an entry to {coercedValues} named {argumentName} with the
568+
value {defaultValue}.
569+
* Otherwise if {argumentType} is a Non-Nullable type, and either {hasValue}
570+
is not {true} or {value} is {null}, throw a field error.
571+
* Otherwise if {hasValue} is true:
572+
* If {value} is {null}:
563573
* Add an entry to {coercedValues} named {argumentName} with the
564574
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}.
575+
* Otherwise, if {argumentValue} is a {Variable}:
570576
* Add an entry to {coercedValues} named {argumentName} with the
571-
value {variableValue}.
572-
* Otherwise, if {argumentType} is a Non-Nullable type, throw a field error.
573-
* Otherwise, if {defaultValue} exists (including {null}):
577+
value {value}.
578+
* Otherwise:
579+
* If {value} cannot be coerced according to the input coercion
580+
rules of {variableType}, throw a field error.
581+
* Let {coercedValue} be the result of coercing {value} according to the
582+
input coercion rules of {variableType}.
574583
* Add an entry to {coercedValues} named {argumentName} with the
575-
value {defaultValue}.
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}.
584+
value {coercedValue}.
583585
* Return {coercedValues}.
584586

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

0 commit comments

Comments
 (0)