-
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] Default value coercion rules #793
base: main
Are you sure you want to change the base?
Changes from 4 commits
8939e20
25da056
f73af73
5e793fb
084b2d5
475d697
dde1f7b
12e26d8
2838c4f
9b7bfd5
bb6ab78
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 |
---|---|---|
|
@@ -818,6 +818,8 @@ of rules must be adhered to by every Object type in a GraphQL schema. | |
characters {"__"} (two underscores). | ||
2. The argument must accept a type where {IsInputType(argumentType)} | ||
returns {true}. | ||
3. If the argument has a default value it must be compatible with | ||
{argumentType} as per the coercion rules for that type. | ||
3. An object type may declare that it implements one or more unique interfaces. | ||
4. An object type must be a super-set of all interfaces it implements: | ||
1. Let this object type be {objectType}. | ||
|
@@ -1520,7 +1522,8 @@ defined by the input object type and for which a value exists. The resulting map | |
is constructed with the following rules: | ||
|
||
* If no value is provided for a defined input object field and that field | ||
definition provides a default value, the default value should be used. If no | ||
definition provides a default value, the result of coercing the default value | ||
according to the coercion rules of the input field type should be used. If no | ||
default value is provided and the input object field's type is non-null, an | ||
error should be thrown. Otherwise, if the field is not required, then no entry | ||
is added to the coerced unordered map. | ||
|
@@ -1580,6 +1583,17 @@ Literal Value | Variables | Coerced Value | |
characters {"__"} (two underscores). | ||
3. The input field must accept a type where {IsInputType(inputFieldType)} | ||
returns {true}. | ||
4. If the input field has a non-null default value: | ||
1. If the input field references this Input Object either directly or | ||
through referenced Input Objects, all input fields in the chain of | ||
references which reference this Input Object must either: | ||
1. have no default value; or | ||
2. have a {null} default value; or | ||
3. have a default value, {nestedDefaultValue}, such that the value for | ||
this field within {nestedDefaultValue} is either {null} or an empty | ||
list. | ||
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. Could this possibly be incorporated into the existing step 3 below? Perhaps:
adds something along the lines of "and have either no default value, or a default value of either {null} or an empty list" 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.
input A {
b: B = {}
}
input B {
c: C = { a: null }
}
input C {
a: A = {}
}
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. Scratch that; I think you're right but the wording you've suggested doesn't quite work (see above example). Maybe:
Bit awkward wording, but hopefully you catch my drift. i.e. allow the following:
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. These are very helpful examples! I was definitely too restrictive in my rule set. I think what I currently have implemented in PR would reject all cycles, but would also incorrectly reject this example. I agree that wording isn't quite right, but it's much closer. I like that this has just one rule about circular references but it makes it clear that there are multiple ways to have illegal circular references - and the above rule can specifically be about type checking the default value 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. just to be clear here: That correct? 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. also: we would have to apply this validation rule also for default variable values, right? 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. I don't think we also need it for variable values or argument defaults since those can only be the entry to a circular ref but not actually contained within a circular ref. input A {
a: A = {}
}
query Test ($x: A = {}) {} This is problematic because I think more generally you can say that given a set of Input Objects that have no circular reference, there exists no default value for arguments or variable values that would create a circular reference. 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.
Coerced default values apply to fields, not types - so: the coerced default value for 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. Okay I've had a go at writing an algorithm for this; I think the key learning whilst attempting this was that the act of referencing the default value a second time is what triggers a cycle; so the algoritm only tracks when the default value of a field is referenced. |
||
2. {defaultValue} must be compatible with {inputFieldType} as per the | ||
coercion rules for that type. | ||
3. If an Input Object references itself either directly or through referenced | ||
Input Objects, at least one of the fields in the chain of references must be | ||
either a nullable or a List type. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -89,8 +89,10 @@ CoerceVariableValues(schema, operation, variableValues): | |
* Let {value} be the value provided in {variableValues} for the | ||
name {variableName}. | ||
* If {hasValue} is not {true} and {defaultValue} exists (including {null}): | ||
* Let {coercedDefaultValue} be the result of coercing {defaultValue} according to the | ||
input coercion rules of {variableType}. | ||
* Add an entry to {coercedValues} named {variableName} with the | ||
value {defaultValue}. | ||
value {coercedDefaultValue}. | ||
* 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: | ||
|
@@ -586,8 +588,10 @@ CoerceArgumentValues(objectType, field, variableValues): | |
name {variableName}. | ||
* Otherwise, let {value} be {argumentValue}. | ||
* If {hasValue} is not {true} and {defaultValue} exists (including {null}): | ||
* Let {coercedDefaultValue} be the result of coercing {defaultValue} according to the | ||
input coercion rules of {argumentType}. | ||
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. NOTE: this is memoizeable and could be computed at schema build time (without affecting the introspection results). 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. TODO: add this as a non-normative note below. |
||
* Add an entry to {coercedValues} named {argumentName} with the | ||
value {defaultValue}. | ||
value {coercedDefaultValue}. | ||
* 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: | ||
|
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.
Should this just be:
Since a
null
default value might fail the inner step 2, "must be compatible with {inputFieldType}" if that type might be a Non-NullThere 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.
The
non-null
is here because if the default for the field isnull
(assuming this is compatible withinputFieldType
), then no further validation is required since no recursion through the default value can occur. Consider the A vs B types in the below scenarios; both are fine, but the validation of A1 can stop at theA1.a2
field immediately, whereas the validation ofB1
has to traverse through B2 and B3 to get back to finding that the nested reference toB1
viaB3.b1
defaults tonull
.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'm less concerned about the recursion (nested step 1) and more about the type correctness (nested step 2)
This implies that for the example:
when validating input field
a
, it would see that the default value isnull
and not progress to determine if it is compatible withString!
(which it would and should fail)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.
This is no longer relevant with the rewriting of the text I think.