Skip to content

Commit

Permalink
Prevent @Skip and @include on root subscription selection set
Browse files Browse the repository at this point in the history
  • Loading branch information
benjie committed Apr 25, 2021
1 parent 6c81ed8 commit 27408f8
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 14 deletions.
32 changes: 24 additions & 8 deletions spec/Section 5 -- Validation.md
Expand Up @@ -249,19 +249,22 @@ query getName {

**Formal Specification**

* For each subscription operation definition {subscription} in the document
* Let {subscriptionType} be the root Subscription type in {schema}.
* Let {selectionSet} be the top level selection set on {subscription}.
* Let {variableValues} be the empty set.
* Let {groupedFieldSet} be the result of
{CollectFields(subscriptionType, selectionSet, variableValues)}.
* {groupedFieldSet} must have exactly one entry, which must not be an
introspection field.
* For each subscription operation definition {subscription} in the document:
* 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)}.
* {groupedFieldSet} must have exactly one entry, which must not be an
introspection field.

**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()}).

Valid examples:

```graphql example
Expand Down Expand Up @@ -312,6 +315,19 @@ fragment multipleSubscriptions on Subscription {
}
```

We do not allow the `@skip` and `@include` directives at the root of the
subscription operation. The following example is also invalid:

```graphql counter-example
subscription requiredRuntimeValidation($bool: Boolean!) {
newMessage @include(if: $bool) {
body
sender
}
disallowedSecondRootField @skip(if: $bool)
}
```

The root field of a subscription operation must not be an introspection field.
The following example is also invalid:

Expand Down
23 changes: 17 additions & 6 deletions spec/Section 6 -- Execution.md
Expand Up @@ -479,17 +479,28 @@ 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 {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 {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} 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 27408f8

Please sign in to comment.