Skip to content

Commit 313562a

Browse files
authored
Merge branch 'main' into maintain-order
2 parents b9725d7 + b1a23de commit 313562a

File tree

6 files changed

+429
-250
lines changed

6 files changed

+429
-250
lines changed

spec/Appendix B -- Grammar Summary.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ lines and uniform indentation with {BlockStringValue()}.
133133

134134
## Document Syntax
135135

136+
Description : StringValue
137+
136138
Document : Definition+
137139

138140
Definition :
@@ -149,7 +151,7 @@ ExecutableDefinition :
149151

150152
OperationDefinition :
151153

152-
- OperationType Name? VariablesDefinition? Directives? SelectionSet
154+
- Description? OperationType Name? VariablesDefinition? Directives? SelectionSet
153155
- SelectionSet
154156

155157
OperationType : one of `query` `mutation` `subscription`
@@ -174,8 +176,8 @@ FragmentSpread : ... FragmentName Directives?
174176

175177
InlineFragment : ... TypeCondition? Directives? SelectionSet
176178

177-
FragmentDefinition : fragment FragmentName TypeCondition Directives?
178-
SelectionSet
179+
FragmentDefinition : Description? fragment FragmentName TypeCondition
180+
Directives? SelectionSet
179181

180182
FragmentName : Name but not `on`
181183

@@ -213,7 +215,8 @@ ObjectField[Const] : Name : Value[?Const]
213215

214216
VariablesDefinition : ( VariableDefinition+ )
215217

216-
VariableDefinition : Variable : Type DefaultValue? Directives[Const]?
218+
VariableDefinition : Description? Variable : Type DefaultValue?
219+
Directives[Const]?
217220

218221
Variable : $ Name
219222

@@ -268,8 +271,6 @@ SchemaExtension :
268271

269272
RootOperationTypeDefinition : OperationType : NamedType
270273

271-
Description : StringValue
272-
273274
TypeDefinition :
274275

275276
- ScalarTypeDefinition

spec/Section 1 -- Overview.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ GraphQL has a number of design principles:
6363
endpoints. A GraphQL response, on the other hand, contains exactly what a
6464
client asks for and no more.
6565

66-
- **Introspective**: GraphQL is introspective. A GraphQL service's type system
67-
can be queryable by the GraphQL language itself, as will be described in this
68-
specification. GraphQL introspection serves as a powerful platform for
69-
building common tools and client software libraries.
66+
- **Self-describing**: GraphQL is self-describing and introspective. A GraphQL
67+
service's type system can be queryable by the GraphQL language itself, which
68+
includes readable documentation. GraphQL introspection serves as a powerful
69+
platform for building common developer tools and client software libraries.
7070

7171
Because of these principles, GraphQL is a powerful and productive environment
7272
for building client applications. Product developers and designers building

spec/Section 2 -- Language.md

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,57 @@ Any {Name} within a GraphQL type system must not start with two underscores
233233
{"\_\_"} unless it is part of the [introspection system](#sec-Introspection) as
234234
defined by this specification.
235235

236+
## Descriptions
237+
238+
Description : StringValue
239+
240+
Documentation is a first-class feature of GraphQL by including written
241+
descriptions on all named definitions in executable {Document} and GraphQL type
242+
systems, which is also made available via introspection ensuring the
243+
documentation of a GraphQL service remains consistent with its capabilities (see
244+
[Type System Descriptions](#sec-Type-System-Descriptions)).
245+
246+
GraphQL descriptions are provided as Markdown (as specified by
247+
[CommonMark](https://commonmark.org/)). Description strings (often
248+
{BlockString}) occur immediately before the definition they describe.
249+
250+
Descriptions in GraphQL executable documents are purely for documentation
251+
purposes. They MUST NOT affect the execution, validation, or response of a
252+
GraphQL document. It is safe to remove all descriptions and comments from
253+
executable documents without changing their behavior or results.
254+
255+
This is an example of a well-described operation:
256+
257+
```graphql example
258+
"""
259+
Request the current status of a time machine and its operator.
260+
You can also check the status for a particular year.
261+
**Warning:** certain years may trigger an anomaly in the space-time continuum.
262+
"""
263+
query GetTimeMachineStatus(
264+
"The unique serial number of the time machine to inspect."
265+
$machineId: ID!
266+
"The year to check the status for."
267+
$year: Int
268+
) {
269+
timeMachine(id: $machineId) {
270+
...TimeMachineDetails
271+
status(year: $year)
272+
}
273+
}
274+
275+
"Details about a time machine and its operator."
276+
fragment TimeMachineDetails on TimeMachine {
277+
id
278+
model
279+
lastMaintenance
280+
operator {
281+
name
282+
licenseLevel
283+
}
284+
}
285+
```
286+
236287
## Document
237288

238289
Document : Definition+
@@ -279,7 +330,7 @@ be executed must also be provided.
279330

280331
OperationDefinition :
281332

282-
- OperationType Name? VariablesDefinition? Directives? SelectionSet
333+
- Description? OperationType Name? VariablesDefinition? Directives? SelectionSet
283334
- SelectionSet
284335

285336
OperationType : one of `query` `mutation` `subscription`
@@ -298,6 +349,10 @@ For example, this mutation operation might "like" a story and then retrieve the
298349
new number of likes:
299350

300351
```graphql example
352+
"""
353+
Mark story 12345 as "liked"
354+
and return the updated number of likes on the story
355+
"""
301356
mutation {
302357
likeStory(storyID: 12345) {
303358
story {
@@ -322,6 +377,8 @@ For example, this unnamed query operation is written via query shorthand.
322377
}
323378
```
324379

380+
Descriptions are not permitted on query shorthand.
381+
325382
Note: many examples below will use the query short-hand syntax.
326383

327384
## Selection Sets
@@ -523,8 +580,8 @@ which returns the result:
523580

524581
FragmentSpread : ... FragmentName Directives?
525582

526-
FragmentDefinition : fragment FragmentName TypeCondition Directives?
527-
SelectionSet
583+
FragmentDefinition : Description? fragment FragmentName TypeCondition
584+
Directives? SelectionSet
528585

529586
FragmentName : Name but not `on`
530587

@@ -570,6 +627,7 @@ query withFragments {
570627
}
571628
}
572629

630+
"Common fields for a user's friends."
573631
fragment friendFields on User {
574632
id
575633
name
@@ -1181,7 +1239,8 @@ Variable : $ Name
11811239

11821240
VariablesDefinition : ( VariableDefinition+ )
11831241

1184-
VariableDefinition : Variable : Type DefaultValue? Directives[Const]?
1242+
VariableDefinition : Description? Variable : Type DefaultValue?
1243+
Directives[Const]?
11851244

11861245
DefaultValue : = Value[Const]
11871246

@@ -1200,7 +1259,10 @@ In this example, we want to fetch a profile picture size based on the size of a
12001259
particular device:
12011260

12021261
```graphql example
1203-
query getZuckProfile($devicePicSize: Int) {
1262+
query getZuckProfile(
1263+
"The size of the profile picture to fetch."
1264+
$devicePicSize: Int
1265+
) {
12041266
user(id: 4) {
12051267
id
12061268
name

spec/Section 3 -- Type System.md

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,20 +50,16 @@ Tools which only seek to produce and extend schema and not execute requests may
5050
choose to only allow {TypeSystemExtensionDocument} and not allow
5151
{ExecutableDefinition} but should provide a descriptive error if present.
5252

53-
## Descriptions
53+
## Type System Descriptions
5454

55-
Description : StringValue
55+
Documentation is a first-class feature of GraphQL type systems, written
56+
immediately alongside definitions in a {TypeSystemDocument} and made available
57+
via introspection.
5658

57-
Documentation is a first-class feature of GraphQL type systems. To ensure the
58-
documentation of a GraphQL service remains consistent with its capabilities,
59-
descriptions of GraphQL definitions are provided alongside their definitions and
60-
made available via introspection.
61-
62-
To allow GraphQL service designers to easily publish documentation alongside the
63-
capabilities of a GraphQL service, GraphQL descriptions are defined using the
64-
Markdown syntax (as specified by [CommonMark](https://commonmark.org/)). In the
65-
type system definition language, these description strings (often {BlockString})
66-
occur immediately before the definition they describe.
59+
[Descriptions](#sec-Descriptions) allow GraphQL service designers to easily
60+
provide documentation which remains consistent with the capabilities of a
61+
GraphQL service. Descriptions should be provided as Markdown (as specified by
62+
[CommonMark](https://commonmark.org/)) for every definition in a type system.
6763

6864
GraphQL schema and all other definitions (e.g. types, fields, arguments, etc.)
6965
which can be described should provide a {Description} unless they are considered
@@ -779,8 +775,8 @@ type Person {
779775
}
780776
```
781777

782-
Valid operations must supply a nested field set for any field that returns an
783-
object, so this operation is not valid:
778+
Valid operations must supply a _selection set_ for every field whose return type
779+
is an object type, so this operation is not valid:
784780

785781
```graphql counter-example
786782
{
@@ -942,6 +938,8 @@ of rules must be adhered to by every Object type in a GraphQL schema.
942938
returns {true}.
943939
4. If argument type is Non-Null and a default value is not defined:
944940
1. The `@deprecated` directive must not be applied to this argument.
941+
5. If the argument has a default value it must be compatible with
942+
{argumentType} as per the coercion rules for that type.
945943
3. An object type may declare that it implements one or more unique interfaces.
946944
4. An object type must be a super-set of all interfaces it implements:
947945
1. Let this object type be {objectType}.
@@ -1662,7 +1660,8 @@ defined by the input object type and for which a value exists. The resulting map
16621660
is constructed with the following rules:
16631661

16641662
- If no value is provided for a defined input object field and that field
1665-
definition provides a default value, the default value should be used. If no
1663+
definition provides a default value, the result of coercing the default value
1664+
according to the coercion rules of the input field type should be used. If no
16661665
default value is provided and the input object field's type is non-null, an
16671666
error should be raised. Otherwise, if the field is not required, then no entry
16681667
is added to the coerced unordered map.
@@ -1727,6 +1726,44 @@ input ExampleInputObject {
17271726
3. If an Input Object references itself either directly or through referenced
17281727
Input Objects, at least one of the fields in the chain of references must be
17291728
either a nullable or a List type.
1729+
4. {InputObjectDefaultValueHasCycle(inputObject)} must be {false}.
1730+
1731+
InputObjectDefaultValueHasCycle(inputObject, defaultValue, visitedFields):
1732+
1733+
- If {defaultValue} is not provided, initialize it to an empty unordered map.
1734+
- If {visitedFields} is not provided, initialize it to the empty set.
1735+
- If {defaultValue} is a list:
1736+
- For each {itemValue} in {defaultValue}:
1737+
- If {InputObjectDefaultValueHasCycle(inputObject, itemValue,
1738+
visitedFields)}, return {true}.
1739+
- Otherwise, if {defaultValue} is an unordered map:
1740+
- For each field {field} in {inputObject}:
1741+
- If {InputFieldDefaultValueHasCycle(field, defaultValue, visitedFields)},
1742+
return {true}.
1743+
- Return {false}.
1744+
1745+
InputFieldDefaultValueHasCycle(field, defaultValue, visitedFields):
1746+
1747+
- Assert: {defaultValue} is an unordered map.
1748+
- Let {fieldType} be the type of {field}.
1749+
- Let {namedFieldType} be the underlying named type of {fieldType}.
1750+
- If {namedFieldType} is not an input object type:
1751+
- Return {false}.
1752+
- Let {fieldName} be the name of {field}.
1753+
- Let {fieldDefaultValue} be the value for {fieldName} in {defaultValue}.
1754+
- If {fieldDefaultValue} exists:
1755+
- Return {InputObjectDefaultValueHasCycle(namedFieldType, fieldDefaultValue,
1756+
visitedFields)}.
1757+
- Otherwise:
1758+
- Let {fieldDefaultValue} be the default value of {field}.
1759+
- If {fieldDefaultValue} does not exist:
1760+
- Return {false}.
1761+
- If {field} is within {visitedFields}:
1762+
- Return {true}.
1763+
- Let {nextVisitedFields} be a new set containing {field} and everything from
1764+
{visitedFields}.
1765+
- Return {InputObjectDefaultValueHasCycle(namedFieldType, fieldDefaultValue,
1766+
nextVisitedFields)}.
17301767

17311768
### Input Object Extensions
17321769

spec/Section 5 -- Validation.md

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ free of any validation errors, and have not changed since.
3232

3333
**Examples**
3434

35-
For this section of this schema, we will assume the following type system in
36-
order to demonstrate examples:
35+
The examples in this section will use the following types:
3736

3837
```graphql example
3938
type Query {
@@ -303,24 +302,25 @@ query getName {
303302
- Let {subscriptionType} be the root Subscription type in {schema}.
304303
- For each subscription operation definition {subscription} in the document:
305304
- Let {selectionSet} be the top level selection set on {subscription}.
306-
- Let {groupedFieldSet} be the result of
305+
- Let {collectedFieldsMap} be the result of
307306
{CollectSubscriptionFields(subscriptionType, selectionSet)}.
308-
- {groupedFieldSet} must have exactly one entry, which must not be an
307+
- {collectedFieldsMap} must have exactly one entry, which must not be an
309308
introspection field.
310309

311310
CollectSubscriptionFields(objectType, selectionSet, visitedFragments):
312311

313312
- If {visitedFragments} is not provided, initialize it to the empty set.
314-
- Initialize {groupedFields} to an empty ordered map of lists.
313+
- Initialize {collectedFieldsMap} to an empty ordered map of ordered sets.
315314
- For each {selection} in {selectionSet}:
316315
- {selection} must not provide the `@skip` directive.
317316
- {selection} must not provide the `@include` directive.
318317
- If {selection} is a {Field}:
319-
- Let {responseKey} be the response key of {selection} (the alias if
318+
- Let {responseName} be the _response name_ of {selection} (the alias if
320319
defined, otherwise the field name).
321-
- Let {groupForResponseKey} be the list in {groupedFields} for
322-
{responseKey}; if no such list exists, create it as an empty list.
323-
- Append {selection} to the {groupForResponseKey}.
320+
- Let {fieldsForResponseKey} be the _field set_ value in
321+
{collectedFieldsMap} for the key {responseName}; otherwise create the
322+
entry with an empty ordered set.
323+
- Add {selection} to the {fieldsForResponseKey}.
324324
- If {selection} is a {FragmentSpread}:
325325
- Let {fragmentSpreadName} be the name of {selection}.
326326
- If {fragmentSpreadName} is in {visitedFragments}, continue with the next
@@ -334,31 +334,31 @@ CollectSubscriptionFields(objectType, selectionSet, visitedFragments):
334334
- If {DoesFragmentTypeApply(objectType, fragmentType)} is {false}, continue
335335
with the next {selection} in {selectionSet}.
336336
- Let {fragmentSelectionSet} be the top-level selection set of {fragment}.
337-
- Let {fragmentGroupedFieldSet} be the result of calling
337+
- Let {fragmentCollectedFieldsMap} be the result of calling
338338
{CollectSubscriptionFields(objectType, fragmentSelectionSet,
339339
visitedFragments)}.
340-
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
341-
- Let {responseKey} be the response key shared by all fields in
342-
{fragmentGroup}.
343-
- Let {groupForResponseKey} be the list in {groupedFields} for
344-
{responseKey}; if no such list exists, create it as an empty list.
345-
- Append all items in {fragmentGroup} to {groupForResponseKey}.
340+
- For each {responseName} and {fragmentFields} in
341+
{fragmentCollectedFieldsMap}:
342+
- Let {fieldsForResponseKey} be the _field set_ value in
343+
{collectedFieldsMap} for the key {responseName}; otherwise create the
344+
entry with an empty ordered set.
345+
- Add each item from {fragmentFields} to {fieldsForResponseKey}.
346346
- If {selection} is an {InlineFragment}:
347347
- Let {fragmentType} be the type condition on {selection}.
348348
- If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType,
349349
fragmentType)} is {false}, continue with the next {selection} in
350350
{selectionSet}.
351351
- Let {fragmentSelectionSet} be the top-level selection set of {selection}.
352-
- Let {fragmentGroupedFieldSet} be the result of calling
352+
- Let {fragmentCollectedFieldsMap} be the result of calling
353353
{CollectSubscriptionFields(objectType, fragmentSelectionSet,
354354
visitedFragments)}.
355-
- For each {fragmentGroup} in {fragmentGroupedFieldSet}:
356-
- Let {responseKey} be the response key shared by all fields in
357-
{fragmentGroup}.
358-
- Let {groupForResponseKey} be the list in {groupedFields} for
359-
{responseKey}; if no such list exists, create it as an empty list.
360-
- Append all items in {fragmentGroup} to {groupForResponseKey}.
361-
- Return {groupedFields}.
355+
- For each {responseName} and {fragmentFields} in
356+
{fragmentCollectedFieldsMap}:
357+
- Let {fieldsForResponseKey} be the _field set_ value in
358+
{collectedFieldsMap} for the key {responseName}; otherwise create the
359+
entry with an empty ordered set.
360+
- Add each item from {fragmentFields} to {fieldsForResponseKey}.
361+
- Return {collectedFieldsMap}.
362362

363363
Note: This algorithm is very similar to {CollectFields()}, it differs in that it
364364
does not have access to runtime variables and thus the `@skip` and `@include`
@@ -584,7 +584,7 @@ should be unambiguous. Therefore any two field selections which might both be
584584
encountered for the same object are only valid if they are equivalent.
585585

586586
During execution, the simultaneous execution of fields with the same response
587-
name is accomplished by {MergeSelectionSets()} and {CollectFields()}.
587+
name is accomplished by performing {CollectSubfields()} before their execution.
588588

589589
For simple hand-written GraphQL, this rule is obviously a clear developer error,
590590
however nested fragments can make this difficult to detect manually.

0 commit comments

Comments
 (0)