Skip to content
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] Prevent @skip and @include on root subscription selection set #860

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 74 additions & 3 deletions spec/Section 5 -- Validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,16 +258,74 @@ query getName {
- Let {subscriptionType} be the root Subscription type in {schema}.
- For each subscription operation definition {subscription} in the document:
- 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)}.
- Let {groupedFieldSet} be the result of
{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.

Valid examples:

```graphql example
Expand Down Expand Up @@ -318,6 +376,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