Skip to content

Commit

Permalink
Use a modified copy of the CollectFields algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
benjie committed May 26, 2021
1 parent 5e742bb commit 0308b27
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 20 deletions.
54 changes: 51 additions & 3 deletions spec/Section 5 -- Validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,17 +253,65 @@ query getName {
* Let {subscriptionType} be the root Subscription type in {schema}.
* Let {selectionSet} be the top level selection set on {subscription}.
* Let {groupedFieldSet} be the result of
{CollectFields(subscriptionType, selectionSet, null)}.
{CollectSubscriptionFields(subscriptionType, selectionSet)}.
* {groupedFieldSet} must have exactly one entry, which must not be an
introspection field.

CollectSubscriptionFields(objectType, selectionSet, visitedFragments):

* If {visitedFragments} is not provided, initialize it to the empty set.
* Initialize {groupedFields} to an empty ordered map of lists.
* For each {selection} in {selectionSet}:
* {selection} must not provide the `@skip` directive.
* {selection} must not provide the `@include` directive.
* If {selection} is a {Field}:
* Let {responseKey} be the response key of {selection} (the alias if defined, otherwise the field name).
* Let {groupForResponseKey} be the list in {groupedFields} for
{responseKey}; if no such list exists, create it as an empty list.
* Append {selection} to the {groupForResponseKey}.
* If {selection} is a {FragmentSpread}:
* Let {fragmentSpreadName} be the name of {selection}.
* If {fragmentSpreadName} is in {visitedFragments}, continue with the
next {selection} in {selectionSet}.
* Add {fragmentSpreadName} to {visitedFragments}.
* Let {fragment} be the Fragment in the current Document whose name is
{fragmentSpreadName}.
* If no such {fragment} exists, continue with the next {selection} in
{selectionSet}.
* Let {fragmentType} be the type condition on {fragment}.
* If {DoesFragmentTypeApply(objectType, fragmentType)} is false, continue
with the next {selection} in {selectionSet}.
* Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
* Let {fragmentGroupedFieldSet} be the result of calling
{CollectSubscriptionFields(objectType, fragmentSelectionSet, visitedFragments)}.
* For each {fragmentGroup} in {fragmentGroupedFieldSet}:
* Let {responseKey} be the response key shared by all fields in {fragmentGroup}.
* Let {groupForResponseKey} be the list in {groupedFields} for
{responseKey}; if no such list exists, create it as an empty list.
* Append all items in {fragmentGroup} to {groupForResponseKey}.
* If {selection} is an {InlineFragment}:
* Let {fragmentType} be the type condition on {selection}.
* If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType, fragmentType)} is false, continue
with the next {selection} in {selectionSet}.
* Let {fragmentSelectionSet} be the top-level selection set of {selection}.
* Let {fragmentGroupedFieldSet} be the result of calling {CollectSubscriptionFields(objectType, fragmentSelectionSet, visitedFragments)}.
* For each {fragmentGroup} in {fragmentGroupedFieldSet}:
* Let {responseKey} be the response key shared by all fields in {fragmentGroup}.
* Let {groupForResponseKey} be the list in {groupedFields} for
{responseKey}; if no such list exists, create it as an empty list.
* Append all items in {fragmentGroup} to {groupForResponseKey}.
* Return {groupedFields}.

Note: This algorithm is very similar to {CollectFields()}, it differs in that
it does not have access to runtime variables and thus the `@skip` and
`@include` directives cannot be used.

**Explanatory Text**

Subscription operations must have exactly one root field.

To enable us to determine this without access to runtime variables, we must
forbid the `@skip` and `@include` directives in the root selection set (by
passing `null` as the `variableValues` argument to {CollectFields()}).
forbid the `@skip` and `@include` directives in the root selection set.

Valid examples:

Expand Down
23 changes: 6 additions & 17 deletions spec/Section 6 -- Execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,28 +479,17 @@ The depth-first-search order of the field groups produced by {CollectFields()}
is maintained through execution, ensuring that fields appear in the executed
response in a stable and predictable order.

When {CollectFields()} is used during validation (see for example the
[single root field](#sec-Single-root-field) subscription operation validation
rule), the runtime value for {variableValues} will not be available - in this
case we set {variableValues} to {null} and forbid the use of the `@skip` and
`@include` directives. During execution, {variableValues} will always be
non-null.

CollectFields(objectType, selectionSet, variableValues, visitedFragments):

* If {visitedFragments} is not provided, initialize it to the empty set.
* Initialize {groupedFields} to an empty ordered map of lists.
* For each {selection} in {selectionSet}:
* If {variableValues} is {null}:
* {selection} must not provide the `@skip` directive.
* {selection} must not provide the `@include` directive.
* Otherwise:
* If {selection} provides the directive `@skip`, let {skipDirective} be that directive.
* If {skipDirective}'s {if} argument is {true} or is a variable in {variableValues} with the value {true}, continue with the next
{selection} in {selectionSet}.
* If {selection} provides the directive `@include`, let {includeDirective} be that directive.
* If {includeDirective}'s {if} argument is not {true} and is not a variable in {variableValues} with the value {true}, continue with the next
{selection} in {selectionSet}.
* If {selection} provides the directive `@skip`, let {skipDirective} be that directive.
* If {skipDirective}'s {if} argument is {true} or is a variable in {variableValues} with the value {true}, continue with the next
{selection} in {selectionSet}.
* If {selection} provides the directive `@include`, let {includeDirective} be that directive.
* If {includeDirective}'s {if} argument is not {true} and is not a variable in {variableValues} with the value {true}, continue with the next
{selection} in {selectionSet}.
* If {selection} is a {Field}:
* Let {responseKey} be the response key of {selection} (the alias if defined, otherwise the field name).
* Let {groupForResponseKey} be the list in {groupedFields} for
Expand Down

0 comments on commit 0308b27

Please sign in to comment.