Skip to content

Commit 7eb8983

Browse files
benjieleebyron
andauthored
[RFC] Default value coercion rules (#793)
* Default value coercion rules * Fix 'must not cause an infinite loop' wording. * Reorder assertions to prevent infinite loop during coercion * Reduce diff * Clarify the default value cycle detection logic * Rewrite default value cycle algorithm in the style of DetectFragmentCycles * Rewrite input object cycle algorithm to match GraphQL.js implementation * Asterisks * Remove unnecessary step * Add non-normative note about memoizing default value coercion * Rather than assertions, use return values. * Fix accidental mutation * Editorial: simplify optimization note --------- Co-authored-by: Lee Byron <lee@leebyron.com>
1 parent fca6653 commit 7eb8983

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

spec/Section 3 -- Type System.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -938,6 +938,8 @@ of rules must be adhered to by every Object type in a GraphQL schema.
938938
returns {true}.
939939
4. If argument type is Non-Null and a default value is not defined:
940940
1. The `@deprecated` directive must not be applied to this argument.
941+
5. If the argument has a default value it must be compatible with
942+
{argumentType} as per the coercion rules for that type.
941943
3. An object type may declare that it implements one or more unique interfaces.
942944
4. An object type must be a super-set of all interfaces it implements:
943945
1. Let this object type be {objectType}.
@@ -1658,7 +1660,8 @@ defined by the input object type and for which a value exists. The resulting map
16581660
is constructed with the following rules:
16591661

16601662
- If no value is provided for a defined input object field and that field
1661-
definition provides a default value, the default value should be used. If no
1663+
definition provides a default value, the result of coercing the default value
1664+
according to the coercion rules of the input field type should be used. If no
16621665
default value is provided and the input object field's type is non-null, an
16631666
error should be raised. Otherwise, if the field is not required, then no entry
16641667
is added to the coerced unordered map.
@@ -1723,6 +1726,44 @@ input ExampleInputObject {
17231726
3. If an Input Object references itself either directly or through referenced
17241727
Input Objects, at least one of the fields in the chain of references must be
17251728
either a nullable or a List type.
1729+
4. {InputObjectDefaultValueHasCycle(inputObject)} must be {false}.
1730+
1731+
InputObjectDefaultValueHasCycle(inputObject, defaultValue, visitedFields):
1732+
1733+
- If {defaultValue} is not provided, initialize it to an empty unordered map.
1734+
- If {visitedFields} is not provided, initialize it to the empty set.
1735+
- If {defaultValue} is a list:
1736+
- For each {itemValue} in {defaultValue}:
1737+
- If {InputObjectDefaultValueHasCycle(inputObject, itemValue,
1738+
visitedFields)}, return {true}.
1739+
- Otherwise, if {defaultValue} is an unordered map:
1740+
- For each field {field} in {inputObject}:
1741+
- If {InputFieldDefaultValueHasCycle(field, defaultValue, visitedFields)},
1742+
return {true}.
1743+
- Return {false}.
1744+
1745+
InputFieldDefaultValueHasCycle(field, defaultValue, visitedFields):
1746+
1747+
- Assert: {defaultValue} is an unordered map.
1748+
- Let {fieldType} be the type of {field}.
1749+
- Let {namedFieldType} be the underlying named type of {fieldType}.
1750+
- If {namedFieldType} is not an input object type:
1751+
- Return {false}.
1752+
- Let {fieldName} be the name of {field}.
1753+
- Let {fieldDefaultValue} be the value for {fieldName} in {defaultValue}.
1754+
- If {fieldDefaultValue} exists:
1755+
- Return {InputObjectDefaultValueHasCycle(namedFieldType, fieldDefaultValue,
1756+
visitedFields)}.
1757+
- Otherwise:
1758+
- Let {fieldDefaultValue} be the default value of {field}.
1759+
- If {fieldDefaultValue} does not exist:
1760+
- Return {false}.
1761+
- If {field} is within {visitedFields}:
1762+
- Return {true}.
1763+
- Let {nextVisitedFields} be a new set containing {field} and everything from
1764+
{visitedFields}.
1765+
- Return {InputObjectDefaultValueHasCycle(namedFieldType, fieldDefaultValue,
1766+
nextVisitedFields)}.
17261767

17271768
### Input Object Extensions
17281769

spec/Section 6 -- Execution.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,10 @@ CoerceVariableValues(schema, operation, variableValues):
110110
- Let {value} be the value provided in {variableValues} for the name
111111
{variableName}.
112112
- If {hasValue} is not {true} and {defaultValue} exists (including {null}):
113+
- Let {coercedDefaultValue} be the result of coercing {defaultValue}
114+
according to the input coercion rules of {variableType}.
113115
- Add an entry to {coercedValues} named {variableName} with the value
114-
{defaultValue}.
116+
{coercedDefaultValue}.
115117
- Otherwise if {variableType} is a Non-Nullable type, and either {hasValue} is
116118
not {true} or {value} is {null}, raise a _request error_.
117119
- Otherwise if {hasValue} is {true}:
@@ -761,8 +763,10 @@ CoerceArgumentValues(objectType, field, variableValues):
761763
{variableName}.
762764
- Otherwise, let {value} be {argumentValue}.
763765
- If {hasValue} is not {true} and {defaultValue} exists (including {null}):
766+
- Let {coercedDefaultValue} be the result of coercing {defaultValue}
767+
according to the input coercion rules of {argumentType}.
764768
- Add an entry to {coercedValues} named {argumentName} with the value
765-
{defaultValue}.
769+
{coercedDefaultValue}.
766770
- Otherwise if {argumentType} is a Non-Nullable type, and either {hasValue} is
767771
not {true} or {value} is {null}, raise an _execution error_.
768772
- Otherwise if {hasValue} is {true}:
@@ -788,6 +792,9 @@ Note: Variable values are not coerced because they are expected to be coerced
788792
before executing the operation in {CoerceVariableValues()}, and valid operations
789793
must only allow usage of variables of appropriate types.
790794

795+
Note: Implementations are encouraged to optimize the coercion of an argument's
796+
default value by doing so only once and caching the resulting coerced value.
797+
791798
### Value Resolution
792799

793800
While nearly all of GraphQL execution can be described generically, ultimately

0 commit comments

Comments
 (0)