Skip to content
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
209 changes: 209 additions & 0 deletions spec/Section 5 -- Validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,7 @@ FieldsInSetCanMerge(set):
{set} including visiting fragments and inline fragments.
- Given each pair of distinct members {fieldA} and {fieldB} in {fieldsForName}:
- {SameResponseShape(fieldA, fieldB)} must be true.
- {SameStreamDirective(fieldA, fieldB)} must be true.
- If the parent types of {fieldA} and {fieldB} are equal or if either is not
an Object Type:
- {fieldA} and {fieldB} must have identical field names.
Expand Down Expand Up @@ -595,6 +596,16 @@ SameResponseShape(fieldA, fieldB):
- If {SameResponseShape(subfieldA, subfieldB)} is {false}, return {false}.
- Return {true}.

SameStreamDirective(fieldA, fieldB):

- If neither {fieldA} nor {fieldB} has a directive named `stream`.
- Return {true}.
- If both {fieldA} and {fieldB} have a directive named `stream`.
- Let {streamA} be the directive named `stream` on {fieldA}.
- Let {streamB} be the directive named `stream` on {fieldB}.
- If {streamA} and {streamB} have identical sets of arguments, return {true}.
- Return {false}.

Note: In prior versions of the spec the term "composite" was used to signal a
type that is either an Object, Interface or Union type.

Expand Down Expand Up @@ -1695,6 +1706,204 @@ query ($foo: Boolean = true, $bar: Boolean = false) {
}
```

### Defer And Stream Directives Are Used On Valid Root Field

**Formal Specification**

- For each operation definition {operation} in the document:
- If the operation type is not subscription or mutation:
- Continue to the next operation definition.
- Let {selectionSet} be the top level selection set on {operation}.
- {ForbidDeferStream(selectionSet)}.

ForbidDeferStream(selectionSet, visitedFragments):

- If {visitedFragments} is not provided, initialize it to the empty set.
- For each {selection} in {selectionSet}:
- If {selection} is a {Field}:
- {selection} must not provide the `@stream` directive.
- If {selection} is a {FragmentSpread}:
- Let {fragmentSpreadName} be the name of {selection}.
- If {fragmentSpreadName} is in {visitedFragments}, continue with the next
{selection} in {selectionSet}.
- {selection} must not provide the `@defer` directive.
- 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 {fragmentSelectionSet} be the selection set of {selection}.
- {ForbidDeferStream(fragmentSelectionSet, visitedFragments)}.
- If {selection} is a {InlineFragment}:
- {selection} must not provide the `@defer` directive.
- Let {fragmentSelectionSet} be the selection set of {selection}.
- {ForbidDeferStream(fragmentSelectionSet, visitedFragments)}.

**Explanatory Text**

The `@defer` and `@stream` directives are not allowed to be used on root fields
of mutation or subscription operations.

For example, the following document will not pass validation because `@defer`
has been used on a root mutation field:

```raw graphql counter-example
mutation {
... @defer {
mutationField
}
}
```

### Defer And Stream Directives Are Used In Valid Operations

**Formal Specification**

- Initialize {visitedFragments} to the empty set.
- For each {operation} in a document:
- If {operation} is not a subscription operation:
- Continue to the next operation definition.
- Let {operationSelection} be the top level selection set on {operation}.
- {ForbidUnconditionalDeferStream(operationSelection, visitedFragments)}.

ForbidUnconditionalDeferStream(selectionSet, visitedFragments):

- For each {selection} in {selectionSet}:
- If {selection} provides the `@skip` directive, and the `if` argument on that
directive is not the boolean value {false}:
- Continue to the next {selection} in {selectionSet}.
- If {selection} provides the `@include` directive, and the `if` argument on
that directive is not the boolean value {true}:
- Continue to the next {selection} in {selectionSet}.
- For each `@defer` or `@stream` {directive} on {selection}:
- Let {if} be the argument named `if` on {directive}.
- {if} must be defined.
- Let {argumentValue} be the value passed to {if}.
- {argumentValue} must not be the boolean value {false}.
- 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 {fragmentSelectionSet} be the selection set of {selection}.
- {ForbidUnconditionalDeferStream(fragmentSelectionSet, visitedFragments)}.
- Otherwise:
- Let {nestedSelectionSet} be the selection set of {selection}.
- If {nestedSelectionSet} exists:
- {ForbidUnconditionalDeferStream(nestedSelectionSet, visitedFragments)}.

**Explanatory Text**

The `@defer` and `@stream` directives can not be used to defer or stream data in
subscription operations. If these directives appear in a subscription operation
they must be disabled using an `if` argument. This rule will not permit any
defer or stream directives on a subscription operation that cannot be disabled
using an `if` argument.

For example, the following document will not pass validation because `@defer`
has been used in a subscription operation with no `if` argument defined:

```raw graphql counter-example
subscription sub {
newMessage {
... @defer {
body
}
}
}
```

### Defer And Stream Directive Labels Are Unique

**Formal Specification**

- Let {labelValues} be an empty set.
- For every `@defer` and `@stream` {directive} in the document:
- Let {label} be the value of {directive}'s `label` argument.
- If {label} does not exist or is {null}:
- Continue to the next {directive}.
- {label} must not be a variable.
- {label} must not be present in {labelValues}.
- Add {label} to {labelValues}.

**Explanatory Text**

The `@defer` and `@stream` directives each accept an argument `label`. This
label may be used by GraphQL clients to uniquely identify response payloads. If
a label is passed, it must not be a variable and it must be unique within all
other `@defer` and `@stream` directives in the document.

For example the following document is valid:

```graphql example
{
dog {
...fragmentOne
...fragmentTwo @defer(label: "dogDefer")
}
pets @stream(label: "petStream") {
name
}
}

fragment fragmentOne on Dog {
name
}

fragment fragmentTwo on Dog {
owner {
name
}
}
```

The following document will not pass validation because the same label is used
in multiple `@defer` and `@stream` directives:

```raw graphql counter-example
{
dog {
...fragmentOne @defer(label: "MyLabel")
}
pets @stream(label: "MyLabel") {
name
}
}

fragment fragmentOne on Dog {
name
}
```

### Stream Directives Are Used On List Fields

**Formal Specification**

- For every `@stream` {directive} in the document:
- Let {adjacent} be the AST node {directive} affects.
- Let {nullableFieldType} be the unwrapped nullable type of {adjacent}.
- {nullableFieldType} must be a List type.

**Explanatory Text**

GraphQL directive locations do not provide enough granularity to distinguish the
type of fields used in a GraphQL document. Since the stream directive is only
valid on list fields, an additional validation rule must be used to ensure it is
used correctly.

For example, the following document will only pass validation if `field`
contains a List type in the associated schema.

```graphql counter-example
query {
field @stream(initialCount: 0)
}
```

## Variables

### Variable Uniqueness
Expand Down
Loading