Skip to content

Commit a44d4ec

Browse files
authored
[RFC] Prevent @Skip and @include on root subscription selection set (#860)
* Prevent @Skip and @include on root subscription selection set * Use a modified copy of the CollectFields algorithm * Fix algorithm format
1 parent 3b0d8e6 commit a44d4ec

File tree

1 file changed

+74
-3
lines changed

1 file changed

+74
-3
lines changed

spec/Section 5 -- Validation.md

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -258,16 +258,74 @@ query getName {
258258
- Let {subscriptionType} be the root Subscription type in {schema}.
259259
- For each subscription operation definition {subscription} in the document:
260260
- Let {selectionSet} be the top level selection set on {subscription}.
261-
- Let {variableValues} be the empty set.
262-
- Let {groupedFieldSet} be the result of {CollectFields(subscriptionType,
263-
selectionSet, variableValues)}.
261+
- Let {groupedFieldSet} be the result of
262+
{CollectSubscriptionFields(subscriptionType, selectionSet)}.
264263
- {groupedFieldSet} must have exactly one entry, which must not be an
265264
introspection field.
266265

266+
CollectSubscriptionFields(objectType, selectionSet, visitedFragments):
267+
268+
- If {visitedFragments} is not provided, initialize it to the empty set.
269+
- Initialize {groupedFields} to an empty ordered map of lists.
270+
- For each {selection} in {selectionSet}:
271+
- {selection} must not provide the `@skip` directive.
272+
- {selection} must not provide the `@include` directive.
273+
- If {selection} is a {Field}:
274+
- Let {responseKey} be the response key of {selection} (the alias if
275+
defined, otherwise the field name).
276+
- Let {groupForResponseKey} be the list in {groupedFields} for
277+
{responseKey}; if no such list exists, create it as an empty list.
278+
- Append {selection} to the {groupForResponseKey}.
279+
- If {selection} is a {FragmentSpread}:
280+
- Let {fragmentSpreadName} be the name of {selection}.
281+
- If {fragmentSpreadName} is in {visitedFragments}, continue with the next
282+
{selection} in {selectionSet}.
283+
- Add {fragmentSpreadName} to {visitedFragments}.
284+
- Let {fragment} be the Fragment in the current Document whose name is
285+
{fragmentSpreadName}.
286+
- If no such {fragment} exists, continue with the next {selection} in
287+
{selectionSet}.
288+
- Let {fragmentType} be the type condition on {fragment}.
289+
- If {DoesFragmentTypeApply(objectType, fragmentType)} is {false}, continue
290+
with the next {selection} in {selectionSet}.
291+
- Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
292+
- Let {fragmentGroupedFieldSet} be the result of calling
293+
{CollectSubscriptionFields(objectType, fragmentSelectionSet,
294+
visitedFragments)}.
295+
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
296+
- Let {responseKey} be the response key shared by all fields in
297+
{fragmentGroup}.
298+
- Let {groupForResponseKey} be the list in {groupedFields} for
299+
{responseKey}; if no such list exists, create it as an empty list.
300+
- Append all items in {fragmentGroup} to {groupForResponseKey}.
301+
- If {selection} is an {InlineFragment}:
302+
- Let {fragmentType} be the type condition on {selection}.
303+
- If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType,
304+
fragmentType)} is {false}, continue with the next {selection} in
305+
{selectionSet}.
306+
- Let {fragmentSelectionSet} be the top-level selection set of {selection}.
307+
- Let {fragmentGroupedFieldSet} be the result of calling
308+
{CollectSubscriptionFields(objectType, fragmentSelectionSet,
309+
visitedFragments)}.
310+
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
311+
- Let {responseKey} be the response key shared by all fields in
312+
{fragmentGroup}.
313+
- Let {groupForResponseKey} be the list in {groupedFields} for
314+
{responseKey}; if no such list exists, create it as an empty list.
315+
- Append all items in {fragmentGroup} to {groupForResponseKey}.
316+
- Return {groupedFields}.
317+
318+
Note: This algorithm is very similar to {CollectFields()}, it differs in that it
319+
does not have access to runtime variables and thus the `@skip` and `@include`
320+
directives cannot be used.
321+
267322
**Explanatory Text**
268323

269324
Subscription operations must have exactly one root field.
270325

326+
To enable us to determine this without access to runtime variables, we must
327+
forbid the `@skip` and `@include` directives in the root selection set.
328+
271329
Valid examples:
272330

273331
```graphql example
@@ -318,6 +376,19 @@ fragment multipleSubscriptions on Subscription {
318376
}
319377
```
320378

379+
We do not allow the `@skip` and `@include` directives at the root of the
380+
subscription operation. The following example is also invalid:
381+
382+
```graphql counter-example
383+
subscription requiredRuntimeValidation($bool: Boolean!) {
384+
newMessage @include(if: $bool) {
385+
body
386+
sender
387+
}
388+
disallowedSecondRootField @skip(if: $bool)
389+
}
390+
```
391+
321392
The root field of a subscription operation must not be an introspection field.
322393
The following example is also invalid:
323394

0 commit comments

Comments
 (0)