From 12b7ad7f0fe6ac3996fd5a2bc564357cd2dcb0bc Mon Sep 17 00:00:00 2001 From: dugenkui Date: Fri, 3 Feb 2023 03:08:37 +0800 Subject: [PATCH 01/27] add explanation about argument name uniqueness. (#891) Co-authored-by: Benjie Gillam --- spec/Section 3 -- Type System.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 7c116bf81..3df677ee6 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -878,9 +878,11 @@ of rules must be adhered to by every Object type in a GraphQL schema. 4. For each argument of the field: 1. The argument must not have a name which begins with the characters {"\_\_"} (two underscores). - 2. The argument must accept a type where {IsInputType(argumentType)} + 2. The argument must have a unique name within that field; no two + arguments may share the same name. + 3. The argument must accept a type where {IsInputType(argumentType)} returns {true}. - 3. If argument type is Non-Null and a default value is not defined: + 4. If argument type is Non-Null and a default value is not defined: - The `@deprecated` directive must not be applied to this argument. 3. An object type may declare that it implements one or more unique interfaces. 4. An object type must be a super-set of all interfaces it implements: @@ -1228,7 +1230,9 @@ Interface types have the potential to be invalid if incorrectly defined. 4. For each argument of the field: 1. The argument must not have a name which begins with the characters {"\_\_"} (two underscores). - 2. The argument must accept a type where {IsInputType(argumentType)} + 2. The argument must have a unique name within that field; no two + arguments may share the same name. + 3. The argument must accept a type where {IsInputType(argumentType)} returns {true}. 3. An interface type may declare that it implements one or more unique interfaces, but may not implement itself. @@ -2004,7 +2008,9 @@ repeatable directives. 4. For each argument of the directive: 1. The argument must not have a name which begins with the characters {"\_\_"} (two underscores). - 2. The argument must accept a type where {IsInputType(argumentType)} returns + 2. The argument must have a unique name within that directive; no two + arguments may share the same name. + 3. The argument must accept a type where {IsInputType(argumentType)} returns {true}. ### @skip From 4e93488096479c3fcfcc905126c8d157ad2e8c4c Mon Sep 17 00:00:00 2001 From: Benjie Date: Thu, 9 Feb 2023 19:26:02 +0000 Subject: [PATCH 02/27] Fix ambiguity around when schema definition may be omitted (#987) Clarify that a schema definition must be present (and not omitted) if a default root type name happens to be used by a type which is not a root type. Also adds an editorial change to introduce term definitions for the immediate concepts involved. Co-authored-by: Lee Byron --- spec/Section 3 -- Type System.md | 67 +++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 3df677ee6..bc4174273 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -122,7 +122,7 @@ RootOperationTypeDefinition : OperationType : NamedType A GraphQL service's collective type system capabilities are referred to as that service's "schema". A schema is defined in terms of the types and directives it -supports as well as the root operation types for each kind of operation: query, +supports as well as the _root operation type_ for each kind of operation: query, mutation, and subscription; this determines the place in the type system where those operations begin. @@ -141,24 +141,24 @@ introspection system. ### Root Operation Types -A schema defines the initial root operation type for each kind of operation it -supports: query, mutation, and subscription; this determines the place in the +:: A schema defines the initial _root operation type_ for each kind of operation +it supports: query, mutation, and subscription; this determines the place in the type system where those operations begin. -The {`query`} root operation type must be provided and must be an Object type. +The {`query`} _root operation type_ must be provided and must be an Object type. -The {`mutation`} root operation type is optional; if it is not provided, the +The {`mutation`} _root operation type_ is optional; if it is not provided, the service does not support mutations. If it is provided, it must be an Object type. -Similarly, the {`subscription`} root operation type is also optional; if it is +Similarly, the {`subscription`} _root operation type_ is also optional; if it is not provided, the service does not support subscriptions. If it is provided, it must be an Object type. The {`query`}, {`mutation`}, and {`subscription`} root types must all be different types if provided. -The fields on the {`query`} root operation type indicate what fields are +The fields on the {`query`} _root operation type_ indicate what fields are available at the top level of a GraphQL query operation. For example, this example operation: @@ -169,7 +169,8 @@ query { } ``` -is only valid when the {`query`} root operation type has a field named "myName": +is only valid when the {`query`} _root operation type_ has a field named +"myName": ```graphql example type Query { @@ -177,8 +178,8 @@ type Query { } ``` -Similarly, the following mutation is only valid if the {`mutation`} root -operation type has a field named "setName". +Similarly, the following mutation is only valid if the {`mutation`} _root +operation type_ has a field named "setName". ```graphql example mutation { @@ -191,8 +192,8 @@ mutation { When using the type system definition language, a document must include at most one {`schema`} definition. -In this example, a GraphQL schema is defined with both query and mutation root -operation types: +In this example, a GraphQL schema is defined with both a query and mutation +_root operation type_: ```graphql example schema { @@ -211,18 +212,22 @@ type MyMutationRootType { **Default Root Operation Type Names** -While any type can be the root operation type for a GraphQL operation, the type -system definition language can omit the schema definition when the {`query`}, -{`mutation`}, and {`subscription`} root types are named {"Query"}, {"Mutation"}, -and {"Subscription"} respectively. +:: The _default root type name_ for each {`query`}, {`mutation`}, and +{`subscription`} _root operation type_ are {"Query"}, {"Mutation"}, and +{"Subscription"} respectively. + +The type system definition language can omit the schema definition when each +_root operation type_ uses its respective _default root type name_ and no other +type uses any _default root type name_. Likewise, when representing a GraphQL schema using the type system definition -language, a schema definition should be omitted if it only uses the default root -operation type names. +language, a schema definition should be omitted if each _root operation type_ +uses its respective _default root type name_ and no other type uses any _default +root type name_. This example describes a valid complete GraphQL schema, despite not explicitly including a {`schema`} definition. The {"Query"} type is presumed to be the -{`query`} root operation type of the schema. +{`query`} _root operation type_ of the schema. ```graphql example type Query { @@ -230,6 +235,30 @@ type Query { } ``` +This example describes a valid GraphQL schema without a {`mutation`} _root +operation type_, even though it contains a type named {"Mutation"}. The schema +definition must be included, otherwise the {"Mutation"} type would be +incorrectly presumed to be the {`mutation`} _root operation type_ of the schema. + +```graphql example +schema { + query: Query +} + +type Query { + latestVirus: Virus +} + +type Virus { + name: String + mutations: [Mutation] +} + +type Mutation { + name: String +} +``` + ### Schema Extension SchemaExtension : From afc0a35d271ba9502c3c68aeda6e6c6fbc223774 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sat, 11 Feb 2023 11:20:12 +1100 Subject: [PATCH 03/27] Add links to contributed custom scalar specs at scalars.graphql.org (#1009) Adds how to contribute custom scalar specs, links to spec templates, and adds examples following the launch of https://scalars.graphql.org Co-authored-by: Benjie --- spec/Section 3 -- Type System.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index bc4174273..bb0d50b35 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -434,12 +434,17 @@ conform to its described rules. ```graphql example scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122") scalar URL @specifiedBy(url: "https://tools.ietf.org/html/rfc3986") +scalar DateTime + @specifiedBy(url: "https://scalars.graphql.org/andimarek/date-time") ``` Custom *scalar specification URL*s should provide a single, stable format to avoid ambiguity. If the linked specification is in flux, the service should link to a fixed version rather than to a resource which might change. +Note: Some community-maintained custom scalar specifications are hosted at +[scalars.graphql.org](https://scalars.graphql.org/). + Custom *scalar specification URL*s should not be changed once defined. Doing so would likely disrupt tooling or could introduce breaking changes within the linked specification's contents. @@ -448,7 +453,9 @@ Built-in scalar types must not provide a _scalar specification URL_ as they are specified by this document. Note: Custom scalars should also summarize the specified format and provide -examples in their description. +examples in their description; see the GraphQL scalars +[implementation guide](https://scalars.graphql.org/implementation-guide) for +more guidance. **Result Coercion and Serialization** @@ -2145,6 +2152,9 @@ behavior of [custom scalar types](#sec-Scalars.Custom-Scalars). The URL should point to a human-readable specification of the data format, serialization, and coercion rules. It must not appear on built-in scalar types. +Note: Details on implementing a GraphQL scalar specification can be found in the +[scalars.graphql.org implementation guide](https://scalars.graphql.org/implementation-guide). + In this example, a custom scalar type for `UUID` is defined with a URL pointing to the relevant IETF specification. From a5cc6ea0c9f3850f58ef1947d8eda8c6e593b95a Mon Sep 17 00:00:00 2001 From: Benjie Date: Sat, 20 May 2023 00:44:58 +0100 Subject: [PATCH 04/27] Clarify that selection sets cannot be empty (#1025) --- spec/Section 3 -- Type System.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index bb0d50b35..d32b08566 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1796,8 +1796,9 @@ to denote a field that uses a Non-Null type like this: `name: String!`. **Nullable vs. Optional** Fields are _always_ optional within the context of a selection set, a field may -be omitted and the selection set is still valid. However fields that return -Non-Null types will never return the value {null} if queried. +be omitted and the selection set is still valid (so long as the selection set +does not become empty). However fields that return Non-Null types will never +return the value {null} if queried. Inputs (such as field arguments), are always optional by default. However a non-null input type is required. In addition to not accepting the value {null}, From 6b7c2c45d7136f7fa727cd64b994d2573d4e8014 Mon Sep 17 00:00:00 2001 From: Roman Ivantsov Date: Sat, 8 Jul 2023 02:20:59 -0700 Subject: [PATCH 05/27] Remove "subscriptions is a significant change" sentence (#983) Co-authored-by: Benjie Co-authored-by: Roman Ivantsov --- spec/Section 6 -- Execution.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 28862ea89..f357069f9 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -224,9 +224,8 @@ must receive no more events from that event stream. **Supporting Subscriptions at Scale** -Supporting subscriptions is a significant change for any GraphQL service. Query -and mutation operations are stateless, allowing scaling via cloning of GraphQL -service instances. Subscriptions, by contrast, are stateful and require +Query and mutation operations are stateless, allowing scaling via cloning of +GraphQL service instances. Subscriptions, by contrast, are stateful and require maintaining the GraphQL document, variables, and other context over the lifetime of the subscription. From 3adfcca73644234fbbbb062a5cec9e7703419a9f Mon Sep 17 00:00:00 2001 From: Benjie Date: Fri, 8 Sep 2023 09:58:54 +0100 Subject: [PATCH 06/27] Add explicit definition for BlockString (#1042) --- spec/Appendix B -- Grammar Summary.md | 4 +++- spec/Section 2 -- Language.md | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index 2291ee35f..92f222cb3 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -100,7 +100,7 @@ StringValue :: - `""` [lookahead != `"`] - `"` StringCharacter+ `"` -- `"""` BlockStringCharacter\* `"""` +- BlockString StringCharacter :: @@ -121,6 +121,8 @@ HexDigit :: one of EscapedCharacter :: one of `"` `\` `/` `b` `f` `n` `r` `t` +BlockString :: `"""` BlockStringCharacter\* `"""` + BlockStringCharacter :: - SourceCharacter but not `"""` or `\"""` diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 1aca650a8..3ac7c7e60 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -806,7 +806,7 @@ StringValue :: - `""` [lookahead != `"`] - `"` StringCharacter+ `"` -- `"""` BlockStringCharacter\* `"""` +- BlockString StringCharacter :: @@ -827,6 +827,8 @@ HexDigit :: one of EscapedCharacter :: one of `"` `\` `/` `b` `f` `n` `r` `t` +BlockString :: `"""` BlockStringCharacter\* `"""` + BlockStringCharacter :: - SourceCharacter but not `"""` or `\"""` @@ -1007,7 +1009,11 @@ StringCharacter :: `\` EscapedCharacter | {`r`} | U+000D | carriage return | | {`t`} | U+0009 | horizontal tab | -StringValue :: `"""` BlockStringCharacter\* `"""` +StringValue :: BlockString + +- Return the _Unicode text_ by evaluating the {BlockString}. + +BlockString :: `"""` BlockStringCharacter\* `"""` - Let {rawValue} be the _Unicode text_ by concatenating the evaluation of all {BlockStringCharacter} (which may be an empty sequence). From 56d61073137caac3dbea6ec8c3652cc3c8b90d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e?= Date: Fri, 10 Nov 2023 11:08:03 +0100 Subject: [PATCH 07/27] Fix heading level for Required Arguments validation rule (#1055) --- spec/Section 5 -- Validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index dceec126b..2000c324c 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -739,7 +739,7 @@ invalid. which contains {argument}. - {arguments} must be the set containing only {argument}. -#### Required Arguments +### Required Arguments - For each Field or Directive in the document: - Let {arguments} be the arguments provided by the Field or Directive. From feac5a54c6a95c1d4f7804bfaeb268c8bd206f2c Mon Sep 17 00:00:00 2001 From: Yaacov Rydzinski Date: Thu, 14 Dec 2023 14:48:30 +0200 Subject: [PATCH 08/27] Fix punctuation in some algorithms (#1067) --- STYLE_GUIDE.md | 5 +++++ spec/Section 2 -- Language.md | 4 ++-- spec/Section 3 -- Type System.md | 4 ++-- spec/Section 5 -- Validation.md | 4 ++-- spec/Section 6 -- Execution.md | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md index 8514811e0..db699fdd5 100644 --- a/STYLE_GUIDE.md +++ b/STYLE_GUIDE.md @@ -55,3 +55,8 @@ hyphens) should be capitalized, with the following exceptions: All elements in hyphenated words follow the same rules, e.g. headings may contain `Non-Null`, `Context-Free`, `Built-in` (`in` is a preposition, so is not capitalized). + +## Lists + +Lists can be written as full sentences or as fragments. Algorithms that appear +as lists, however, should be written in full sentences with proper punctuation. diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 3ac7c7e60..ce4823aed 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -1120,7 +1120,7 @@ ListValue : [ Value+ ] - For each {Value+} - Let {value} be the result of evaluating {Value}. - Append {value} to {inputList}. -- Return {inputList} +- Return {inputList}. ### Input Object Values @@ -1168,7 +1168,7 @@ ObjectValue : { ObjectField+ } - Let {name} be {Name} in {field}. - Let {value} be the result of evaluating {Value} in {field}. - Add a field to {inputObject} of name {name} containing value {value}. -- Return {inputObject} +- Return {inputObject}. ## Variables diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index d32b08566..58ed8d5cb 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -354,7 +354,7 @@ IsInputType(type) : - Return IsInputType({unwrappedType}) - If {type} is a Scalar, Enum, or Input Object type: - Return {true} -- Return {false} +- Return {false}. IsOutputType(type) : @@ -363,7 +363,7 @@ IsOutputType(type) : - Return IsOutputType({unwrappedType}) - If {type} is a Scalar, Object, Interface, Union, or Enum type: - Return {true} -- Return {false} +- Return {false}. ### Type Extensions diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 2000c324c..467590876 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -1014,7 +1014,7 @@ is a validation error if the target of a spread is not defined. - For each {fragmentDefinition} in the document: - Let {visited} be the empty set. - - {DetectFragmentCycles(fragmentDefinition, visited)} + - {DetectFragmentCycles(fragmentDefinition, visited)}. DetectFragmentCycles(fragmentDefinition, visited): @@ -1023,7 +1023,7 @@ DetectFragmentCycles(fragmentDefinition, visited): - {visited} must not contain {spread}. - Let {nextVisited} be the set including {spread} and members of {visited}. - Let {nextFragmentDefinition} be the target of {spread}. - - {DetectFragmentCycles(nextFragmentDefinition, nextVisited)} + - {DetectFragmentCycles(nextFragmentDefinition, nextVisited)}. **Explanatory Text** diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index f357069f9..7ee850dce 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -263,7 +263,7 @@ CreateSourceEventStream(subscription, schema, variableValues, initialValue): is unaffected if an alias is used. - Let {field} be the first entry in {fields}. - Let {argumentValues} be the result of {CoerceArgumentValues(subscriptionType, - field, variableValues)} + field, variableValues)}. - Let {fieldStream} be the result of running {ResolveFieldEventStream(subscriptionType, initialValue, fieldName, argumentValues)}. From 8682a86cd66cdc6d054c678a3f3ef7a32457e5fc Mon Sep 17 00:00:00 2001 From: Benjie Date: Thu, 7 Mar 2024 19:21:35 +0000 Subject: [PATCH 09/27] Fix 'response error' -> 'request error' (#1016) --- spec/Section 3 -- Type System.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 58ed8d5cb..107253251 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1631,7 +1631,7 @@ The value for an input object should be an input object literal or an unordered map supplied by a variable, otherwise a _request error_ must be raised. In either case, the input object literal or unordered map must not contain any entries with names not defined by a field of this input object type, otherwise a -response error must be raised. +request error must be raised. The result of coercion is an unordered map with an entry for each field both defined by the input object type and for which a value exists. The resulting map From 0ba7cdf781125779498df3b45a5b97a697a439ba Mon Sep 17 00:00:00 2001 From: Benjie Date: Thu, 7 Mar 2024 19:33:25 +0000 Subject: [PATCH 10/27] Enforce consistent punctuation in algorithms (#1069) * Consistent punctuation in algorithms * Clarify style guide * Boolean formatting * Add a format check to CI --- .github/algorithm-format-check.mjs | 114 +++++++++++++++++++++++++++++ .github/workflows/ci.yml | 1 + STYLE_GUIDE.md | 28 ++++++- package.json | 1 + spec/Section 2 -- Language.md | 20 ++--- spec/Section 3 -- Type System.md | 16 ++-- spec/Section 4 -- Introspection.md | 16 ++-- spec/Section 5 -- Validation.md | 12 +-- spec/Section 6 -- Execution.md | 21 +++--- 9 files changed, 184 insertions(+), 45 deletions(-) create mode 100644 .github/algorithm-format-check.mjs diff --git a/.github/algorithm-format-check.mjs b/.github/algorithm-format-check.mjs new file mode 100644 index 000000000..13a31ece9 --- /dev/null +++ b/.github/algorithm-format-check.mjs @@ -0,0 +1,114 @@ +import { readFile, readdir } from "node:fs/promises"; + +const SPEC_DIR = new URL("../spec", import.meta.url).pathname; + +process.exitCode = 0; +const filenames = await readdir(SPEC_DIR); +for (const filename of filenames) { + if (!filename.endsWith(".md")) { + continue; + } + const markdown = await readFile(`${SPEC_DIR}/${filename}`, "utf8"); + + /** + * Not strictly 'lines' since we try and group indented things together as if + * they were one line. Close enough though. + */ + const lines = markdown.split(/\n(?=[\S\n]|\s*(?:-|[0-9]+\.) )/); + + for (let i = 0, l = lines.length; i < l; i++) { + const line = lines[i]; + + // Check algorithm is consistently formatted + { + // Is it an algorithm definition? + const matches = line.match(/^([a-z0-9A-Z]+)(\s*)\(([^)]*)\)(\s*):(\s*)$/); + if (matches) { + const [, algorithmName, ns1, _args, ns2, ns3] = matches; + if (ns1 || ns2 || ns3) { + console.log( + `Bad whitespace in definition of ${algorithmName} in '${filename}':` + ); + console.log(line); + console.log(); + process.exitCode = 1; + } + if (lines[i + 1] !== "") { + console.log( + `No empty space after algorithm ${algorithmName} header in '${filename}'` + ); + console.log(); + process.exitCode = 1; + } + for (let j = i + 2; j < l; j++) { + const step = lines[j]; + if (!step.match(/^\s*(-|[0-9]+\.) /)) { + if (step !== "") { + console.log( + `Bad algorithm ${algorithmName} step in '${filename}':` + ); + console.log(step); + console.log(); + process.exitCode = 1; + } + break; + } + if (!step.match(/[.:]$/)) { + console.log( + `Bad formatting for '${algorithmName}' step (does not end in '.' or ':') in '${filename}':` + ); + console.log(step); + console.log(); + process.exitCode = 1; + } + if (step.match(/^\s*(-|[0-9]\.)\s+[a-z]/)) { + console.log( + `Bad formatting of '${algorithmName}' step (should start with a capital) in '${filename}':` + ); + console.log(step); + console.log(); + process.exitCode = 1; + } + const trimmedInnerLine = step.replace(/\s+/g, " "); + if ( + trimmedInnerLine.match( + /(?:[rR]eturn|is (?:not )?)(true|false|null)\b/ + ) && + !trimmedInnerLine.match(/null or empty/) + ) { + console.log( + `Potential bad formatting of '${algorithmName}' step (true/false/null should be wrapped in curly braces, e.g. '{true}') in '${filename}':` + ); + console.log(step); + console.log(); + process.exitCode = 1; + } + } + } + } + + // Check `- ...:` step is followed by an indent + { + const matches = line.match(/^(\s*)- .*:\s*$/); + if (matches) { + const indent = matches[1]; + const nextLine = lines[i + 1]; + if (!nextLine.startsWith(`${indent} `)) { + console.log( + `Lacking indent in '${filename}' following ':' character:` + ); + console.dir(line); + console.dir(nextLine); + console.log(); + // TODO: process.exitCode = 1; + } + } + } + } +} + +if (process.exitCode === 0) { + console.log(`Everything looks okay!`); +} else { + console.log(`Please resolve the errors detailed above.`); +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be594a4bd..f56e99d2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,7 @@ jobs: - uses: actions/setup-node@v3 - run: npm ci - run: npm run test:format + - run: npm run test:algorithm-format test-build: runs-on: ubuntu-latest steps: diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md index db699fdd5..7ee0e2915 100644 --- a/STYLE_GUIDE.md +++ b/STYLE_GUIDE.md @@ -56,7 +56,29 @@ All elements in hyphenated words follow the same rules, e.g. headings may contain `Non-Null`, `Context-Free`, `Built-in` (`in` is a preposition, so is not capitalized). -## Lists +## Algorithms -Lists can be written as full sentences or as fragments. Algorithms that appear -as lists, however, should be written in full sentences with proper punctuation. +A named algorithm definition starts with the name of the algorithm in +`PascalCase`, an open parenthesis, a comma-and-space separated list of +arguments, a close parenthesis and then a colon. It is followed by a blank +newline and a list of steps in the algorithm which may be numbered or bulleted. + +Each step in an algorithm should either end in a colon (`:`) with an indented +step on the next line, or a fullstop (`.`). (A step after a step ending in a +full stop may or may not be indented, use your discretion.) + +Indentation in algorithms is significant. + +Every step in an algorithm should start with a capital letter. + +``` +MyAlgorithm(argOne, argTwo): + +- Let {something} be {true}. +- For each {arg} in {argOne}: + - If {arg} is greater than {argTwo}: + - Let {something} be {false}. + - Otherwise if {arg} is less than {argTwo}: + - Let {something} be {true}. +- Return {something}. +``` diff --git a/package.json b/package.json index 7a4dd650b..0d05daa05 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "test:spelling": "cspell \"spec/**/*.md\" README.md", "format": "prettier --write \"**/*.{md,yml,yaml,json}\"", "test:format": "prettier --check \"**/*.{md,yml,yaml,json}\" || npm run suggest:format", + "test:algorithm-format": "node .github/algorithm-format-check.mjs", "suggest:format": "echo \"\nTo resolve this, run: $(tput bold)npm run format$(tput sgr0)\" && exit 1", "build": "./build.sh", "test:build": "spec-md --metadata spec/metadata.json spec/GraphQL.md > /dev/null", diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index ce4823aed..59dc0f8aa 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -1032,7 +1032,7 @@ BlockStringValue(rawValue): - Let {lines} be the result of splitting {rawValue} by {LineTerminator}. - Let {commonIndent} be {null}. - For each {line} in {lines}: - - If {line} is the first item in {lines}, continue to the next line. + - If {line} is the first item in {lines}, continue to the next {line}. - Let {length} be the number of characters in {line}. - Let {indent} be the number of leading consecutive {WhiteSpace} characters in {line}. @@ -1117,7 +1117,7 @@ ListValue : [ ] ListValue : [ Value+ ] - Let {inputList} be a new empty list value. -- For each {Value+} +- For each {Value+}: - Let {value} be the result of evaluating {Value}. - Append {value} to {inputList}. - Return {inputList}. @@ -1164,7 +1164,7 @@ ObjectValue : { } ObjectValue : { ObjectField+ } - Let {inputObject} be a new input object value with no fields. -- For each {field} in {ObjectField+} +- For each {field} in {ObjectField+}: - Let {name} be {Name} in {field}. - Let {value} be the result of evaluating {Value} in {field}. - Add a field to {inputObject} of name {name} containing value {value}. @@ -1247,22 +1247,22 @@ input type. Type : Name -- Let {name} be the string value of {Name} +- Let {name} be the string value of {Name}. - Let {type} be the type defined in the Schema named {name} -- {type} must not be {null} -- Return {type} +- {type} must not be {null}. +- Return {type}. Type : [ Type ] -- Let {itemType} be the result of evaluating {Type} +- Let {itemType} be the result of evaluating {Type}. - Let {type} be a List type where {itemType} is the contained type. -- Return {type} +- Return {type}. Type : Type ! -- Let {nullableType} be the result of evaluating {Type} +- Let {nullableType} be the result of evaluating {Type}. - Let {type} be a Non-Null type where {nullableType} is the contained type. -- Return {type} +- Return {type}. ## Directives diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 107253251..497ad3ab2 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -347,22 +347,22 @@ can only be used as input types. Object, Interface, and Union types can only be used as output types. Lists and Non-Null types may be used as input types or output types depending on how the wrapped type may be used. -IsInputType(type) : +IsInputType(type): - If {type} is a List type or Non-Null type: - Let {unwrappedType} be the unwrapped type of {type}. - - Return IsInputType({unwrappedType}) + - Return IsInputType({unwrappedType}). - If {type} is a Scalar, Enum, or Input Object type: - - Return {true} + - Return {true}. - Return {false}. -IsOutputType(type) : +IsOutputType(type): - If {type} is a List type or Non-Null type: - Let {unwrappedType} be the unwrapped type of {type}. - - Return IsOutputType({unwrappedType}) + - Return IsOutputType({unwrappedType}). - If {type} is a Scalar, Object, Interface, Union, or Enum type: - - Return {true} + - Return {true}. - Return {false}. ### Type Extensions @@ -919,7 +919,7 @@ of rules must be adhered to by every Object type in a GraphQL schema. 3. The argument must accept a type where {IsInputType(argumentType)} returns {true}. 4. If argument type is Non-Null and a default value is not defined: - - The `@deprecated` directive must not be applied to this argument. + 1. The `@deprecated` directive must not be applied to this argument. 3. An object type may declare that it implements one or more unique interfaces. 4. An object type must be a super-set of all interfaces it implements: 1. Let this object type be {objectType}. @@ -1699,7 +1699,7 @@ input ExampleInputObject { 3. The input field must accept a type where {IsInputType(inputFieldType)} returns {true}. 4. If input field type is Non-Null and a default value is not defined: - - The `@deprecated` directive must not be applied to this input field. + 1. The `@deprecated` directive must not be applied to this input field. 3. If an Input Object references itself either directly or through referenced Input Objects, at least one of the fields in the chain of references must be either a nullable or a List type. diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 3054a9f6c..6fb31b820 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -414,8 +414,8 @@ The `__Field` type represents each field in an Object or Interface type. Fields\: -- `name` must return a String -- `description` may return a String or {null} +- `name` must return a String. +- `description` may return a String or {null}. - `args` returns a List of `__InputValue` representing the arguments this field accepts. - Accepts the argument `includeDeprecated` which defaults to {false}. If @@ -433,8 +433,8 @@ The `__InputValue` type represents field and directive arguments as well as the Fields\: -- `name` must return a String -- `description` may return a String or {null} +- `name` must return a String. +- `description` may return a String or {null}. - `type` must return a `__Type` that represents the type this input value expects. - `defaultValue` may return a String encoding (using the GraphQL language) of @@ -451,8 +451,8 @@ The `__EnumValue` type represents one of possible values of an enum. Fields\: -- `name` must return a String -- `description` may return a String or {null} +- `name` must return a String. +- `description` may return a String or {null}. - `isDeprecated` returns {true} if this enum value should no longer be used, otherwise {false}. - `deprecationReason` optionally provides a reason why this enum value is @@ -489,8 +489,8 @@ supported. All possible locations are listed in the `__DirectiveLocation` enum: Fields\: -- `name` must return a String -- `description` may return a String or {null} +- `name` must return a String. +- `description` may return a String or {null}. - `locations` returns a List of `__DirectiveLocation` representing the valid locations this directive may be placed. - `args` returns a List of `__InputValue` representing the arguments this diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 467590876..473cf5457 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -435,25 +435,25 @@ SameResponseShape(fieldA, fieldB): - Let {typeA} be the return type of {fieldA}. - Let {typeB} be the return type of {fieldB}. - If {typeA} or {typeB} is Non-Null: - - If {typeA} or {typeB} is nullable, return false. + - If {typeA} or {typeB} is nullable, return {false}. - Let {typeA} be the nullable type of {typeA}. - Let {typeB} be the nullable type of {typeB}. - If {typeA} or {typeB} is List: - - If {typeA} or {typeB} is not List, return false. + - If {typeA} or {typeB} is not List, return {false}. - Let {typeA} be the item type of {typeA}. - Let {typeB} be the item type of {typeB}. - Repeat from step 3. - If {typeA} or {typeB} is Scalar or Enum: - - If {typeA} and {typeB} are the same type return true, otherwise return - false. + - If {typeA} and {typeB} are the same type return {true}, otherwise return + {false}. - Assert: {typeA} and {typeB} are both composite types. - Let {mergedSet} be the result of adding the selection set of {fieldA} and the selection set of {fieldB}. - Let {fieldsForName} be the set of selections with a given response name in {mergedSet} including visiting fragments and inline fragments. - Given each pair of members {subfieldA} and {subfieldB} in {fieldsForName}: - - If {SameResponseShape(subfieldA, subfieldB)} is false, return false. -- Return true. + - If {SameResponseShape(subfieldA, subfieldB)} is {false}, return {false}. +- Return {true}. **Explanatory Text** diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 7ee850dce..8b22faec1 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -96,7 +96,7 @@ CoerceVariableValues(schema, operation, variableValues): {defaultValue}. - Otherwise if {variableType} is a Non-Nullable type, and either {hasValue} is not {true} or {value} is {null}, raise a _request error_. - - Otherwise if {hasValue} is true: + - Otherwise if {hasValue} is {true}: - If {value} is {null}: - Add an entry to {coercedValues} named {variableName} with the value {null}. @@ -173,7 +173,8 @@ Subscribe(subscription, schema, variableValues, initialValue): - Let {sourceStream} be the result of running {CreateSourceEventStream(subscription, schema, variableValues, initialValue)}. - Let {responseStream} be the result of running - {MapSourceToResponseEvent(sourceStream, subscription, schema, variableValues)} + {MapSourceToResponseEvent(sourceStream, subscription, schema, + variableValues)}. - Return {responseStream}. Note: In a large-scale subscription system, the {Subscribe()} and @@ -319,7 +320,7 @@ the subscription. Unsubscribe(responseStream): -- Cancel {responseStream} +- Cancel {responseStream}. ## Executing Selection Sets @@ -520,7 +521,7 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - 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 + - 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 @@ -535,7 +536,7 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - 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 + 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 @@ -552,13 +553,13 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): DoesFragmentTypeApply(objectType, fragmentType): - If {fragmentType} is an Object Type: - - if {objectType} and {fragmentType} are the same type, return {true}, + - If {objectType} and {fragmentType} are the same type, return {true}, otherwise return {false}. - If {fragmentType} is an Interface Type: - - if {objectType} is an implementation of {fragmentType}, return {true} + - If {objectType} is an implementation of {fragmentType}, return {true} otherwise return {false}. - If {fragmentType} is a Union: - - if {objectType} is a possible type of {fragmentType}, return {true} + - If {objectType} is a possible type of {fragmentType}, return {true} otherwise return {false}. Note: The steps in {CollectFields()} evaluating the `@skip` and `@include` @@ -577,7 +578,7 @@ ExecuteField(objectType, objectValue, fieldType, fields, variableValues): - Let {field} be the first entry in {fields}. - Let {fieldName} be the field name of {field}. - Let {argumentValues} be the result of {CoerceArgumentValues(objectType, field, - variableValues)} + variableValues)}. - Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName, argumentValues)}. - Return the result of {CompleteValue(fieldType, fields, resolvedValue, @@ -619,7 +620,7 @@ CoerceArgumentValues(objectType, field, variableValues): {defaultValue}. - Otherwise if {argumentType} is a Non-Nullable type, and either {hasValue} is not {true} or {value} is {null}, raise a _field error_. - - Otherwise if {hasValue} is true: + - Otherwise if {hasValue} is {true}: - If {value} is {null}: - Add an entry to {coercedValues} named {argumentName} with the value {null}. From b850f4a76c69d15c7b8f3c165695b9114e52dacc Mon Sep 17 00:00:00 2001 From: mjmahone Date: Mon, 2 Jan 2023 17:45:26 -0500 Subject: [PATCH 11/27] RFC: Fragment Arguments --- spec/Appendix B -- Grammar Summary.md | 6 +- spec/Section 2 -- Language.md | 73 ++++++++- spec/Section 5 -- Validation.md | 227 ++++++++++++++++++++++---- spec/Section 6 -- Execution.md | 53 +++++- 4 files changed, 307 insertions(+), 52 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index 92f222cb3..22c30704b 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -170,12 +170,12 @@ Arguments[Const] : ( Argument[?Const]+ ) Argument[Const] : Name : Value[?Const] -FragmentSpread : ... FragmentName Directives? +FragmentSpread : ... FragmentName Arguments? Directives? InlineFragment : ... TypeCondition? Directives? SelectionSet -FragmentDefinition : fragment FragmentName TypeCondition Directives? -SelectionSet +FragmentDefinition : fragment FragmentName VariablesDefinition? TypeCondition +Directives? SelectionSet FragmentName : Name but not `on` diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 59dc0f8aa..6dbe29547 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -516,10 +516,10 @@ which returns the result: ## Fragments -FragmentSpread : ... FragmentName Directives? +FragmentSpread : ... FragmentName Arguments? Directives? -FragmentDefinition : fragment FragmentName TypeCondition Directives? -SelectionSet +FragmentDefinition : fragment FragmentName VariablesDefinition? TypeCondition +Directives? SelectionSet FragmentName : Name but not `on` @@ -1215,13 +1215,70 @@ size `60`: **Variable Use Within Fragments** -Variables can be used within fragments. Variables have global scope with a given -operation, so a variable used within a fragment must be declared in any -top-level operation that transitively consumes that fragment. If a variable is -referenced in a fragment and is included by an operation that does not define -that variable, that operation is invalid (see +Variables can be used within fragments. Operation-defined variables have global +scope with a given operation, so a variable used within a fragment must either +be declared in any top-level operation that transitively consumes that fragment, +or by that same fragment as a fragment variable definition. If a variable is +referenced in a fragment is included by an operation where neither the fragment +nor the operation defines that variable, that operation is invalid (see [All Variable Uses Defined](#sec-All-Variable-Uses-Defined)). +## Fragment Variable Definitions + +Fragments may define locally scoped variables. This allows fragments to be +reused while enabling the caller to specify the fragment's behavior. + +For example, the profile picture may need to be a different size depending on +the parent context: + +```graphql example +query withFragmentArguments { + user(id: 4) { + ...dynamicProfilePic(size: 100) + friends(first: 10) { + id + name + ...dynamicProfilePic + } + } +} + +fragment dynamicProfilePic($size: Int! = 50) on User { + profilePic(size: $size) +} +``` + +In this case the `user` will have a larger `profilePic` than those found in the +list of `friends`. + +A fragment-defined variable is scoped to the fragment that defines it. +Fragment-defined variables are allowed to shadow operation-defined variables. + +```graphql example +query withShadowedVariables($size: Int) { + user(id: 4) { + ...variableProfilePic + } + secondUser: user(id: 5) { + ...dynamicProfilePic(size: 10) + } +} + +fragment variableProfilePic on User { + ...dynamicProfilePic(size: $size) +} + +fragment dynamicProfilePic($size: Int!) on User { + profilePic(size: $size) +} +``` + +The profilePic for `user` will be determined by the variables set by the +operation, while `secondUser` will always have a profilePic of size 10. In this +case, the fragment `variableProfilePic` uses the operation-defined variable, +while `dynamicProfilePic` uses the value passed in via the fragment spread's +argument `size`. + ## Type References Type : diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 473cf5457..a5cdeaf94 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -418,8 +418,15 @@ fragment directFieldSelectionOnUnion on CatOrDog { FieldsInSetCanMerge(set): +- Let {visitedSelections} be the selections in {set} including visiting + fragments and inline fragments and applying any supplied fragment spread + arguments. +- Let {spreadsForName} be the set of fragment spreads with a given name in + {visitedSelections}. +- Given each pair of members {spreadA} and {spreadB} in {spreadsForName}: + - {spreadA} and {spreadB} must have identical sets of arguments. - Let {fieldsForName} be the set of selections with a given response name in - {set} including visiting fragments and inline fragments. + {visitedSelections}. - Given each pair of members {fieldA} and {fieldB} in {fieldsForName}: - {SameResponseShape(fieldA, fieldB)} must be true. - If the parent types of {fieldA} and {fieldB} are equal or if either is not @@ -570,6 +577,50 @@ fragment conflictingDifferingResponses on Pet { } ``` +Fragment spread arguments can also cause fields to fail to merge. + +While the following is valid: + +```graphql example +fragment commandFragment($command: DogCommand!) on Dog { + doesKnowCommand(dogCommand: $command) +} + +fragment potentiallyConflictingArguments( + $commandOne: DogCommand! + $commandTwo: DogCommand! +) on Dog { + ...commandFragment(command: $commandOne) + ...commandFragment(command: $commandTwo) +} + +fragment safeFragmentArguments on Dog { + ...potentiallyConflictingArguments(commandOne: SIT, commandTwo: SIT) +} +``` + +it is only valid because `safeFragmentArguments` uses +`potentiallyConflictingArguments` with the same value for the fragment-defined +variables `commandOne` and `commandTwo`. Therefore `commandFragment` resolves +`doesKnowCommand`'s `dogCommand` argument value to `SIT` in both cases. + +However, by changing the fragment spread argument values: + +```graphql counter-example +fragment conflictingFragmentArguments on Dog { + ...potentiallyConflictingArguments(commandOne: SIT, commandTwo: DOWN) +} +``` + +the response will have two conflicting versions of the `doesKnowCommand` +fragment that cannot merge. + +If two fragment spreads with the same name supply different argument values, +their fields will not be able to merge. In this case, validation fails because +the fragment spread `...commandFragment(command: SIT)` and +`...commandFragment(command: DOWN)` are part of the visited selections that will +be merged. + ### Leaf Field Selections **Formal Specification** @@ -647,8 +698,8 @@ query directQueryOnObjectWithSubFields { ## Arguments -Arguments are provided to both fields and directives. The following validation -rules apply in both cases. +Arguments are provided to fields, fragment spreads and directives. The following +validation rules apply in each case. ### Argument Names @@ -656,14 +707,16 @@ rules apply in both cases. - For each {argument} in the document: - Let {argumentName} be the Name of {argument}. - - Let {argumentDefinition} be the argument definition provided by the parent - field or definition named {argumentName}. + - If the parent is a field or directive: + - Let {argumentDefinition} be the argument or variable definition named + {argumentName} provided by the parent field definition, directive definition + or fragment definition. - {argumentDefinition} must exist. **Explanatory Text** -Every argument provided to a field or directive must be defined in the set of -possible arguments of that field or directive. +Every argument provided to a field or directive or fragment spread must be +defined in the set of possible arguments of that field, directive or fragment. For example the following are valid: @@ -675,9 +728,18 @@ fragment argOnRequiredArg on Dog { fragment argOnOptional on Dog { isHouseTrained(atOtherHomes: true) @include(if: true) } + +fragment withFragmentArg($command: DogCommand) on Dog { + doesKnowCommand(dogCommand: $command) +} + +fragment usesFragmentArg on Dog { + ...withFragmentArg(command: DOWN) +} ``` -the following is invalid since `command` is not defined on `DogCommand`. +The following is invalid since `command` is not defined on +`Dog.doesKnowCommand`. ```graphql counter-example fragment invalidArgName on Dog { @@ -685,6 +747,15 @@ fragment invalidArgName on Dog { } ``` +and this is also invalid as the variable `dogCommand` is not defined on fragment +`withFragmentArg`. + +```graphql counter-example +fragment invalidFragmentArgName on Dog { + ...withFragmentArg(dogCommand: SIT) +} +``` + and this is also invalid as `unless` is not defined on `@include`. ```graphql counter-example @@ -727,9 +798,9 @@ fragment multipleArgsReverseOrder on Arguments { ### Argument Uniqueness -Fields and directives treat arguments as a mapping of argument name to value. -More than one argument with the same name in an argument set is ambiguous and -invalid. +Fields, fragment spreads and directives treat arguments as a mapping of argument +name to value. More than one argument with the same name in an argument set is +ambiguous and invalid. **Formal Specification** @@ -741,10 +812,11 @@ invalid. ### Required Arguments -- For each Field or Directive in the document: - - Let {arguments} be the arguments provided by the Field or Directive. +- For each Field, Fragment Spread or Directive in the document: + - Let {arguments} be the arguments provided by the Field, Directive or + Fragment Spread. - Let {argumentDefinitions} be the set of argument definitions of that Field - or Directive. + or Directive, or the variable definitions of that Fragment. - For each {argumentDefinition} in {argumentDefinitions}: - Let {type} be the expected type of {argumentDefinition}. - Let {defaultValue} be the default value of {argumentDefinition}. @@ -1523,18 +1595,19 @@ query ($foo: Boolean = true, $bar: Boolean = false) { **Formal Specification** -- For every {operation} in the document: - - For every {variable} defined on {operation}: +- For every {operation} and {fragment} in the document: + - Let {operationOrFragment} be that {operation} or {fragment}. + - For every {variable} defined on {operationOrFragment}: - Let {variableName} be the name of {variable}. - Let {variables} be the set of all variables named {variableName} on - {operation}. + {operationOrFragment}. - {variables} must be a set of one. **Explanatory Text** -If any operation defines more than one variable with the same name, it is -ambiguous and invalid. It is invalid even if the type of the duplicate variable -is the same. +If any operation or fragment defines more than one variable with the same name, +it is ambiguous and invalid. It is invalid even if the type of the duplicate +variable is the same. ```graphql counter-example query houseTrainedQuery($atOtherHomes: Boolean, $atOtherHomes: Boolean) { @@ -1563,12 +1636,36 @@ fragment HouseTrainedFragment on Query { } ``` +Likewise, it is valid for both an operation and a fragment to define a variable +with the same name: + +```graphql example +query C($atOtherHomes: Boolean) { + ...HouseTrainedFragment + aDog: dog { + ...HouseTrainedDog + } +} + +fragment HouseTrainedDog($atOtherHomes: Boolean) on Dog { + isHouseTrained(atOtherHomes: $atOtherHomes) +} +``` + +Fragment-defined variables are scoped locally to the fragment that defines them, +and override any operation-defined variable values, so there is never ambiguity +about which value to use. In this case, the value of the argument `atOtherHomes` +within `HouseTrainedFragment` will be the operation-set value, and within +`HouseTrainedDog` will resolve to `null`, as the argument is not set by the +fragment spread in the query `C`. + ### Variables Are Input Types **Formal Specification** -- For every {operation} in a {document}: - - For every {variable} on each {operation}: +- For every {operation} and {fragment} in a {document}: + - Let {operationOrFragment} be that {operation} or {fragment}. + - For every {variable} defined on {operationOrFragment}: - Let {variableType} be the type of {variable}. - {IsInputType(variableType)} must be {true}. @@ -1636,13 +1733,14 @@ query takesCatOrDog($catOrDog: CatOrDog) { transitively. - For each {fragment} in {fragments}: - For each {variableUsage} in scope of {fragment}, variable must be in - {operation}'s variable list. + {fragment}'s or {operation}'s variable list. **Explanatory Text** -Variables are scoped on a per-operation basis. That means that any variable used -within the context of an operation must be defined at the top level of that -operation +Operation-defined Variables are scoped on a per-operation basis, while +Fragment-defined Variables are scoped locally to the fragment. That means that +any variable used within the context of an operation must either be defined at +the top level of that operation or on the fragment that uses that variable. For example: @@ -1669,9 +1767,10 @@ query variableIsNotDefined { ${atOtherHomes} is not defined by the operation. Fragments complicate this rule. Any fragment transitively included by an -operation has access to the variables defined by that operation. Fragments can -appear within multiple operations and therefore variable usages must correspond -to variable definitions in all of those operations. +operation has access to the variables defined by that operation and defined on +the fragment. Fragments can appear within multiple operations and therefore +variable usages not defined on the fragment must correspond to variable +definitions in all of those operations. For example the following is valid: @@ -1768,7 +1867,7 @@ This is because {houseTrainedQueryTwoNotDefined} does not define a variable ${atOtherHomes} but that variable is used by {isHouseTrainedFragment} which is included in that operation. -### All Variables Used +### All Operation Variables Used **Formal Specification** @@ -1776,7 +1875,7 @@ included in that operation. - Let {variables} be the variables defined by that {operation}. - Each {variable} in {variables} must be used at least once in either the operation scope itself or any fragment transitively referenced by that - operation. + operation, excluding fragments that define the same name as an argument. **Explanatory Text** @@ -1828,6 +1927,29 @@ fragment isHouseTrainedWithoutVariableFragment on Dog { } ``` +Fragment arguments can shadow operation variables: fragments that use an +argument are not using the operation-defined variable of the same name. + +Likewise, it would be invalid if the variable was shadowed by a fragment +argument: + +```graphql counter-example +query variableNotUsedWithinFragment($atOtherHomes: Boolean) { + dog { + ...shadowedVariableFragment + } +} + +fragment shadowedVariableFragment($atOtherHomes: Boolean) on Dog { + isHouseTrained(atOtherHomes: $atOtherHomes) +} +``` + +because +{$atOtherHomes} is only referenced in a fragment that defines it as a +locally scoped argument, the operation-defined {$atOtherHomes} +variable is never used. + All operations in a document must use all of their variables. As a result, the following document does not validate. @@ -1853,6 +1975,41 @@ fragment isHouseTrainedFragment on Dog { This document is not valid because {queryWithExtraVar} defines an extraneous variable. +### All Fragment Variables Used + +**Formal Specification** + +- For every {fragment} in the document: + - Let {variables} be the variables defined by that {fragment}. + - Each {variable} in {variables} must be used at least once in the fragment's + scope. + +**Explanatory Text** + +All variables defined by a fragment must be used in that same fragment. Because +fragment-defined variables are scoped to the fragment they are defined on, if +the fragment does not use the variable, then the variable definition is +superfluous. + +For example, the following is invalid: + +```graphql counter-example +query queryWithFragmentArgUnused($atOtherHomes: Boolean) { + dog { + ...fragmentArgUnused(atOtherHomes: $atOtherHomes) + } +} + +fragment fragmentArgUnused($atOtherHomes: Boolean) on Dog { + isHouseTrained +} +``` + +This document is invalid: even though `fragmentArgUnused` is spread with the +argument `atOtherHomes` and `$atOtherHomes` is defined as an operation variable, +there is never a variable `$atOtherHomes` used within the scope of +`fragmentArgUnused`. + ### All Variable Usages Are Allowed **Formal Specification** @@ -1861,8 +2018,12 @@ variable. - Let {variableUsages} be all usages transitively included in the {operation}. - For each {variableUsage} in {variableUsages}: - Let {variableName} be the name of {variableUsage}. - - Let {variableDefinition} be the {VariableDefinition} named {variableName} - defined within {operation}. + - If the usage is within a {fragment} that defines a {variableDefinition} + for {variableName}: + - Let {variableDefinition} be the {VariableDefinition} named + {variableName} defined within {fragment}. + - Otherwise, let {variableDefinition} be the {VariableDefinition} named + {variableName} defined within {operation}. - {IsVariableUsageAllowed(variableDefinition, variableUsage)} must be {true}. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 8b22faec1..372d4cd4f 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -513,17 +513,23 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - 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 {arguments} be the set of arguments on {selection}. + - Let {fragmentSpreadKey} be a unique key of {fragmentSpreadName} and + {arguments}. + - If {fragmentSpreadKey} is in {visitedFragments}, continue with the next + {selection} in {selectionSet}. + - Add {fragmentSpreadKey} to {visitedFragments}. - 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 {fragmentWithArgumentSubstitutions} be the result of calling + {SubstituteFragmentArguments(fragment, arguments)}. + - Let {fragmentSelectionSet} be the top-level selection set of + {fragmentWithArgumentSubstitutions}. - Let {fragmentGroupedFieldSet} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)}. @@ -562,9 +568,36 @@ DoesFragmentTypeApply(objectType, fragmentType): - If {objectType} is a possible type of {fragmentType}, return {true} otherwise return {false}. +SubstituteFragmentArguments(fragment, arguments): + +- Let {variablesToSubstitute} be initialized to an empty map. +- For each {variableDefinition} in {fragment}: + - Let {variableName} be the name of {variableDefinition}. + - If {variableName} is a key in {arguments}: + - Let {argumentValue} be the value of {variableName} in {arguments}. + - Add {argumentValue} to {variablesToSubstitute} at key {variableName}. + - Otherwise if {variableDefinition} has a default value {defaultValue}: + - Add {defaultValue} to {variablesToSubstitute} at key {variableName}. + - Otherwise: + - Set the key {variableName} in {variableToSubstitute} to a value indicating + the variable is unset. +- Let {substitutedFragment} be a copy of {fragment} where: + - For each {variable} in the selection set of {fragment}: + - Let {variableUsageName} be the name of {variable}. + - If {variableUsageName} is in {variablesToSubstitute}: + - Replace {variable} with the value of {variableUsageName} in + {variablesToSubstitute}. +- Return {substitutedFragment}. + Note: The steps in {CollectFields()} evaluating the `@skip` and `@include` directives may be applied in either order since they apply commutatively. +Note: The unset value used to replace unset fragment-defined arguments in +{SubstituteFragmentArguments()} must not be a variable defined by any operation +that includes the fragment. An example would be to use a variable with a +reserved prefix, like `$__UNSET`, to replace all unset fragment-defined +variables. + ## Executing Fields Each field requested in the grouped field set that is defined on the selected @@ -577,8 +610,8 @@ ExecuteField(objectType, objectValue, fieldType, fields, variableValues): - Let {field} be the first entry in {fields}. - Let {fieldName} be the field name of {field}. -- Let {argumentValues} be the result of {CoerceArgumentValues(objectType, field, - variableValues)}. +- Let {argumentValues} be the result of {CoerceFieldArgumentValues(objectType, + field, variableValues)}. - Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName, argumentValues)}. - Return the result of {CompleteValue(fieldType, fields, resolvedValue, @@ -593,13 +626,17 @@ the type system to have a specific input type. At each argument position in an operation may be a literal {Value}, or a {Variable} to be provided at runtime. -CoerceArgumentValues(objectType, field, variableValues): +CoerceFieldArgumentValues(objectType, field, variableValues): -- Let {coercedValues} be an empty unordered Map. - Let {argumentValues} be the argument values provided in {field}. - Let {fieldName} be the name of {field}. - Let {argumentDefinitions} be the arguments defined by {objectType} for the field named {fieldName}. +- Return {CoerceArgumentValues(argumentDefinitions, argumentValues, + variableValues)} + +CoerceArgumentValues(argumentDefinitions, argumentValues, variableValues): + - For each {argumentDefinition} in {argumentDefinitions}: - Let {argumentName} be the name of {argumentDefinition}. - Let {argumentType} be the expected type of {argumentDefinition}. From 76011b0388b2a241b8738aadfb229e2c9adfefb1 Mon Sep 17 00:00:00 2001 From: jdecroock Date: Wed, 7 Feb 2024 10:46:28 +0100 Subject: [PATCH 12/27] address https://github.com/graphql/graphql-js/pull/3835#discussion_r1101825832 --- spec/Section 6 -- Execution.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 372d4cd4f..82561928b 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -517,12 +517,9 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): {fragmentSpreadName}. - If no such {fragment} exists, continue with the next {selection} in {selectionSet}. - - Let {arguments} be the set of arguments on {selection}. - - Let {fragmentSpreadKey} be a unique key of {fragmentSpreadName} and - {arguments}. - - If {fragmentSpreadKey} is in {visitedFragments}, continue with the next + - If {fragmentSpreadName} is in {visitedFragments}, continue with the next {selection} in {selectionSet}. - - Add {fragmentSpreadKey} to {visitedFragments}. + - Add {fragmentSpreadName} to {visitedFragments}. - Let {fragmentType} be the type condition on {fragment}. - If {DoesFragmentTypeApply(objectType, fragmentType)} is {false}, continue with the next {selection} in {selectionSet}. From 08d3d324b3d3b4030d1350008fe6f1fe24fc7ba4 Mon Sep 17 00:00:00 2001 From: jdecroock Date: Wed, 7 Feb 2024 10:56:41 +0100 Subject: [PATCH 13/27] wip --- spec/Section 6 -- Execution.md | 66 +++++++++++++++++----------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 82561928b..0bdc93d9a 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -340,11 +340,12 @@ ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues): - For each {groupedFieldSet} as {responseKey} and {fields}: - Let {fieldName} be the name of the first entry in {fields}. Note: This value is unaffected if an alias is used. + - Let {fragmentVariableValues} be the fragment-variables value of the first entry in {fields}. - Let {fieldType} be the return type defined for the field {fieldName} of {objectType}. - If {fieldType} is defined: - Let {responseValue} be {ExecuteField(objectType, objectValue, fieldType, - fields, variableValues)}. + fields, variableValues, fragmentVariableValues)}. - Set {responseValue} as the value for {responseKey} in {resultMap}. - Return {resultMap}. @@ -490,7 +491,7 @@ 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. -CollectFields(objectType, selectionSet, variableValues, visitedFragments): +CollectFields(objectType, selectionSet, variableValues, visitedFragments, localVariableValues): - If {visitedFragments} is not provided, initialize it to the empty set. - Initialize {groupedFields} to an empty ordered map of lists. @@ -498,19 +499,19 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - 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} + {localVariableValues} or {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 + in {localVariableValues} or {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 {responseKey}; if no such list exists, create it as an empty list. - - Append {selection} to the {groupForResponseKey}. + - Append {selection} and {localVariableValues} to the {groupForResponseKey}. - If {selection} is a {FragmentSpread}: - Let {fragmentSpreadName} be the name of {selection}. - Let {fragment} be the Fragment in the current Document whose name is @@ -523,10 +524,8 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - Let {fragmentType} be the type condition on {fragment}. - If {DoesFragmentTypeApply(objectType, fragmentType)} is {false}, continue with the next {selection} in {selectionSet}. - - Let {fragmentWithArgumentSubstitutions} be the result of calling - {SubstituteFragmentArguments(fragment, arguments)}. - - Let {fragmentSelectionSet} be the top-level selection set of - {fragmentWithArgumentSubstitutions}. + - Let {localVariableValues} be the result of calling + {getArgumentValuesFromSpread(fragmentSpread, schema, fragmentDefinition.variableDefinitions, variableValues, localVariableValues)}. - Let {fragmentGroupedFieldSet} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)}. @@ -565,26 +564,25 @@ DoesFragmentTypeApply(objectType, fragmentType): - If {objectType} is a possible type of {fragmentType}, return {true} otherwise return {false}. -SubstituteFragmentArguments(fragment, arguments): +getArgumentValuesFromSpread(fragmentSpread, schema, fragmentDefinitionVariableDefinitions, variableValues, fragmentArgumentValues): -- Let {variablesToSubstitute} be initialized to an empty map. -- For each {variableDefinition} in {fragment}: +- Let {coercedValues} be an empty unordered Map. +- For each {variableDefinition} in {fragmentDefinitionVariableDefinitions}: - Let {variableName} be the name of {variableDefinition}. - - If {variableName} is a key in {arguments}: - - Let {argumentValue} be the value of {variableName} in {arguments}. - - Add {argumentValue} to {variablesToSubstitute} at key {variableName}. - - Otherwise if {variableDefinition} has a default value {defaultValue}: - - Add {defaultValue} to {variablesToSubstitute} at key {variableName}. - - Otherwise: - - Set the key {variableName} in {variableToSubstitute} to a value indicating - the variable is unset. -- Let {substitutedFragment} be a copy of {fragment} where: - - For each {variable} in the selection set of {fragment}: - - Let {variableUsageName} be the name of {variable}. - - If {variableUsageName} is in {variablesToSubstitute}: - - Replace {variable} with the value of {variableUsageName} in - {variablesToSubstitute}. -- Return {substitutedFragment}. + - Let {variableType} be the type of {variableDefinition}. + - Let {defaultValue} be the default value for {variableDefinition}. + - Let {argumentNode} be the node provided in the fragment-spread for {variableName} + - If {argumentNode} isn't present or is null + - If {defaultValue} exists + - Add an entry to {coercedValues} named {argumentName} with the value + {defaultValue}. + - If {variableType} is not-nullable raise a field-error + - Let {hasValue} be {true} if {fragmentArgumentValues} or {variableValues} provides a value for the name + {variableName}. + - If {variableType} is not-nullable and {hasValue} is {false} raise a field-error + - Add an entry to {coercedValues} named {argumentName} with the value + found in {variableValues} or {fragmentArgumentValues}. +- Return {coercedValues}. Note: The steps in {CollectFields()} evaluating the `@skip` and `@include` directives may be applied in either order since they apply commutatively. @@ -603,12 +601,12 @@ coerces any provided argument values, then resolves a value for the field, and finally completes that value either by recursively executing another selection set or coercing a scalar value. -ExecuteField(objectType, objectValue, fieldType, fields, variableValues): +ExecuteField(objectType, objectValue, fieldType, fields, variableValues, fragmentVariableValues): - Let {field} be the first entry in {fields}. - Let {fieldName} be the field name of {field}. - Let {argumentValues} be the result of {CoerceFieldArgumentValues(objectType, - field, variableValues)}. + field, variableValues, fragmentVariableValues)} - Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName, argumentValues)}. - Return the result of {CompleteValue(fieldType, fields, resolvedValue, @@ -623,16 +621,16 @@ the type system to have a specific input type. At each argument position in an operation may be a literal {Value}, or a {Variable} to be provided at runtime. -CoerceFieldArgumentValues(objectType, field, variableValues): +CoerceFieldArgumentValues(objectType, field, variableValues, fragmentVariableValues): - Let {argumentValues} be the argument values provided in {field}. - Let {fieldName} be the name of {field}. - Let {argumentDefinitions} be the arguments defined by {objectType} for the field named {fieldName}. - Return {CoerceArgumentValues(argumentDefinitions, argumentValues, - variableValues)} + variableValues, fragmentVariableValues)} -CoerceArgumentValues(argumentDefinitions, argumentValues, variableValues): +CoerceArgumentValues(argumentDefinitions, argumentValues, variableValues, fragmentVariableValues): - For each {argumentDefinition} in {argumentDefinitions}: - Let {argumentName} be the name of {argumentDefinition}. @@ -644,6 +642,10 @@ CoerceArgumentValues(argumentDefinitions, argumentValues, variableValues): {argumentName}. - If {argumentValue} is a {Variable}: - Let {variableName} be the name of {argumentValue}. + - Let {hasValue} be {true} if {fragmentVariableValues} provides a value for the name + {variableName}. + - Let {value} be the value provided in {fragmentVariableValues} for the name + {variableName}. - Let {hasValue} be {true} if {variableValues} provides a value for the name {variableName}. - Let {value} be the value provided in {variableValues} for the name From 55841763214135a2711bdc542b818bf38d2aa495 Mon Sep 17 00:00:00 2001 From: jdecroock Date: Mon, 12 Feb 2024 10:14:39 +0100 Subject: [PATCH 14/27] wording --- spec/Section 6 -- Execution.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 0bdc93d9a..2f6055392 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -576,10 +576,10 @@ getArgumentValuesFromSpread(fragmentSpread, schema, fragmentDefinitionVariableDe - If {defaultValue} exists - Add an entry to {coercedValues} named {argumentName} with the value {defaultValue}. - - If {variableType} is not-nullable raise a field-error + - If {variableType} is non-nullable raise a field-error - Let {hasValue} be {true} if {fragmentArgumentValues} or {variableValues} provides a value for the name {variableName}. - - If {variableType} is not-nullable and {hasValue} is {false} raise a field-error + - If {variableType} is non-nullable and {hasValue} is {false} raise a field-error - Add an entry to {coercedValues} named {argumentName} with the value found in {variableValues} or {fragmentArgumentValues}. - Return {coercedValues}. From 4dd3fe310c5c0bb887683b43f70ea9862a38493c Mon Sep 17 00:00:00 2001 From: jdecroock Date: Fri, 16 Feb 2024 08:35:18 +0100 Subject: [PATCH 15/27] corrections --- spec/Section 6 -- Execution.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 2f6055392..62de9f61e 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -525,7 +525,7 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments, localV - If {DoesFragmentTypeApply(objectType, fragmentType)} is {false}, continue with the next {selection} in {selectionSet}. - Let {localVariableValues} be the result of calling - {getArgumentValuesFromSpread(fragmentSpread, schema, fragmentDefinition.variableDefinitions, variableValues, localVariableValues)}. + {getArgumentValuesFromSpread(selection, fragmentDefinition, variableValues, localVariableValues)}. - Let {fragmentGroupedFieldSet} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)}. @@ -564,10 +564,10 @@ DoesFragmentTypeApply(objectType, fragmentType): - If {objectType} is a possible type of {fragmentType}, return {true} otherwise return {false}. -getArgumentValuesFromSpread(fragmentSpread, schema, fragmentDefinitionVariableDefinitions, variableValues, fragmentArgumentValues): +getArgumentValuesFromSpread(fragmentSpread, fragmentDefinition, variableValues, fragmentArgumentValues): - Let {coercedValues} be an empty unordered Map. -- For each {variableDefinition} in {fragmentDefinitionVariableDefinitions}: +- For each {variableDefinition} in {fragmentDefinition}: - Let {variableName} be the name of {variableDefinition}. - Let {variableType} be the type of {variableDefinition}. - Let {defaultValue} be the default value for {variableDefinition}. From 440aa9ce6a6471c1573cdfa1cd965b610b5fd5db Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Tue, 27 Feb 2024 21:00:51 +0100 Subject: [PATCH 16/27] Update spec/Section 2 -- Language.md Co-authored-by: Matt Mahoney --- spec/Section 2 -- Language.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 6dbe29547..874da1906 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -1218,7 +1218,7 @@ size `60`: Variables can be used within fragments. Operation-defined variables have global scope with a given operation, so a variable used within a fragment must either be declared in any top-level operation that transitively consumes that fragment, -or by that same fragment as a fragment variable definition. If a variable is +or by that same fragment as a fragment variable definition. If a variable referenced in a fragment is included by an operation where neither the fragment nor the operation defines that variable, that operation is invalid (see [All Variable Uses Defined](#sec-All-Variable-Uses-Defined)). From 48fa841ae59036d7721b3b5d2838f01a9f346eda Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 8 Mar 2024 07:59:31 +0100 Subject: [PATCH 17/27] address validation comments --- spec/Section 5 -- Validation.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index a5cdeaf94..44183b4ef 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -424,7 +424,7 @@ FieldsInSetCanMerge(set): - Let {spreadsForName} be the set of fragment spreads with a given name in {visitedSelections}. - Given each pair of members {spreadA} and {spreadB} in {spreadsForName}: - - {spreadA} and {spreadB} must have identical sets of arguments. + - {spreadA} and {spreadB} must have identical sets of arguments, values and directives. - Let {fieldsForName} be the set of selections with a given response name in {visitedSelections}. - Given each pair of members {fieldA} and {fieldB} in {fieldsForName}: @@ -1636,8 +1636,8 @@ fragment HouseTrainedFragment on Query { } ``` -Likewise, it is valid for both an operation and a fragment to define a variable -with the same name: +Likewise, it is valid for a fragment to define a variable with a name that +is also defined on an operation: ```graphql example query C($atOtherHomes: Boolean) { From d7590fa8eb50a157fbd257eed0a7371a6f37a24e Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 8 Mar 2024 08:03:06 +0100 Subject: [PATCH 18/27] address language comments --- spec/Section 2 -- Language.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 874da1906..29ba56281 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -1216,7 +1216,8 @@ size `60`: **Variable Use Within Fragments** Variables can be used within fragments. Operation-defined variables have global -scope with a given operation, so a variable used within a fragment must either +scope with a given operation. Fragment-defined variables have local scope within the +fragment definition they are defined in. A variable used within a fragment must either be declared in any top-level operation that transitively consumes that fragment, or by that same fragment as a fragment variable definition. If a variable referenced in a fragment is included by an operation where neither the fragment From 78b5c56a84946f5ef4b746abaaa394038c780cbe Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Tue, 26 Mar 2024 21:15:23 +0100 Subject: [PATCH 19/27] Remove unused `$__UNSET` --- spec/Section 6 -- Execution.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 62de9f61e..6eada1599 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -587,12 +587,6 @@ getArgumentValuesFromSpread(fragmentSpread, fragmentDefinition, variableValues, Note: The steps in {CollectFields()} evaluating the `@skip` and `@include` directives may be applied in either order since they apply commutatively. -Note: The unset value used to replace unset fragment-defined arguments in -{SubstituteFragmentArguments()} must not be a variable defined by any operation -that includes the fragment. An example would be to use a variable with a -reserved prefix, like `$__UNSET`, to replace all unset fragment-defined -variables. - ## Executing Fields Each field requested in the grouped field set that is defined on the selected From 7cad3839591fb9681c27e8fedf6e94c14e3fb1d2 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Wed, 27 Mar 2024 13:17:38 +0100 Subject: [PATCH 20/27] Apply Benjie's suggestions Co-authored-by: Benjie --- spec/Section 2 -- Language.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 29ba56281..00038f469 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -1216,9 +1216,9 @@ size `60`: **Variable Use Within Fragments** Variables can be used within fragments. Operation-defined variables have global -scope with a given operation. Fragment-defined variables have local scope within the -fragment definition they are defined in. A variable used within a fragment must either -be declared in any top-level operation that transitively consumes that fragment, +scope within a given operation. Fragment-defined variables have local scope within the +fragment definition in which they are defined. A variable used within a fragment must either +be declared in each top-level operation that transitively consumes that fragment, or by that same fragment as a fragment variable definition. If a variable referenced in a fragment is included by an operation where neither the fragment nor the operation defines that variable, that operation is invalid (see @@ -1233,7 +1233,7 @@ For example, the profile picture may need to be a different size depending on the parent context: ```graphql example -query withFragmentArguments { +query userAndFriends { user(id: 4) { ...dynamicProfilePic(size: 100) friends(first: 10) { @@ -1256,7 +1256,7 @@ A fragment-defined variable is scoped to the fragment that defines it. Fragment-defined variables are allowed to shadow operation-defined variables. ```graphql example -query withShadowedVariables($size: Int) { +query withShadowedVariables($size: Int!) { user(id: 4) { ...variableProfilePic } @@ -1275,10 +1275,10 @@ fragment dynamicProfilePic($size: Int!) on User { ``` The profilePic for `user` will be determined by the variables set by the -operation, while `secondUser` will always have a profilePic of size 10. In this +operation, while `secondUser` will always have a `profilePic` of size `10`. In this case, the fragment `variableProfilePic` uses the operation-defined variable, while `dynamicProfilePic` uses the value passed in via the fragment spread's -argument `size`. +`size` argument. ## Type References From 03ba25598f7520c05b3dfb27697a84de76829c2e Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Wed, 27 Mar 2024 14:38:26 +0100 Subject: [PATCH 21/27] conciser validation --- spec/Section 5 -- Validation.md | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 44183b4ef..6c1c3e4b7 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -579,8 +579,6 @@ fragment conflictingDifferingResponses on Pet { Fragment spread arguments can also cause fields to fail to merge. -While the following is valid: - ```graphql example fragment commandFragment($command: DogCommand!) on Dog { doesKnowCommand(dogCommand: $command) @@ -588,39 +586,25 @@ fragment commandFragment($command: DogCommand!) on Dog { fragment potentiallyConflictingArguments( $commandOne: DogCommand! - $commandTwo: DogCommand! ) on Dog { ...commandFragment(command: $commandOne) ...commandFragment(command: $commandTwo) } fragment safeFragmentArguments on Dog { - ...potentiallyConflictingArguments(commandOne: SIT, commandTwo: SIT) -} -``` - -it is only valid because `safeFragmentArguments` uses -`potentiallyConflictingArguments` with the same value for the fragment-defined -variables `commandOne` and `commandTwo`. Therefore `commandFragment` resolves -`doesKnowCommand`'s `dogCommand` argument value to `SIT` in both cases. - -However, by changing the fragment spread argument values: - -```graphql counter-example -fragment conflictingFragmentArguments on Dog { ...potentiallyConflictingArguments(commandOne: SIT, commandTwo: DOWN) } ``` -the response will have two conflicting versions of the `doesKnowCommand` -fragment that cannot merge. - If two fragment spreads with the same name supply different argument values, their fields will not be able to merge. In this case, validation fails because the fragment spread `...commandFragment(command: SIT)` and `...commandFragment(command: DOWN)` are part of the visited selections that will be merged. +If both of these spreads would have `$commandOne` or `$commandTwo` as the argument-value, +it would be allowed as we can be sure that we'd resolve identical fields. + ### Leaf Field Selections **Formal Specification** From 08927c63f7d322ef915a4a07cd647335250ba6dd Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 29 Mar 2024 11:25:44 +0100 Subject: [PATCH 22/27] Apply suggestions from code review Co-authored-by: Benjie --- spec/Section 5 -- Validation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 6c1c3e4b7..01e2124d9 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -423,8 +423,8 @@ FieldsInSetCanMerge(set): arguments. - Let {spreadsForName} be the set of fragment spreads with a given name in {visitedSelections}. -- Given each pair of members {spreadA} and {spreadB} in {spreadsForName}: - - {spreadA} and {spreadB} must have identical sets of arguments, values and directives. +- For each {spreadsForName} as {name} and {spreads}: + - Each entry in {spreads} must have identical sets of arguments to each other entry in {spreads}. - Let {fieldsForName} be the set of selections with a given response name in {visitedSelections}. - Given each pair of members {fieldA} and {fieldB} in {fieldsForName}: From 3bc4e592271fefdc2b02b9ec042131427ba93616 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Fri, 29 Mar 2024 11:27:23 +0100 Subject: [PATCH 23/27] Apply suggestions from code review Co-authored-by: Benjie --- spec/Section 5 -- Validation.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 01e2124d9..18421899e 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -425,7 +425,7 @@ FieldsInSetCanMerge(set): {visitedSelections}. - For each {spreadsForName} as {name} and {spreads}: - Each entry in {spreads} must have identical sets of arguments to each other entry in {spreads}. -- Let {fieldsForName} be the set of selections with a given response name in +- Let {fieldsForName} be the set of field selections with a given response name in {visitedSelections}. - Given each pair of members {fieldA} and {fieldB} in {fieldsForName}: - {SameResponseShape(fieldA, fieldB)} must be true. @@ -731,7 +731,7 @@ fragment invalidArgName on Dog { } ``` -and this is also invalid as the variable `dogCommand` is not defined on fragment +and this is also invalid as the argument `dogCommand` is not defined on fragment `withFragmentArg`. ```graphql counter-example @@ -1989,10 +1989,7 @@ fragment fragmentArgUnused($atOtherHomes: Boolean) on Dog { } ``` -This document is invalid: even though `fragmentArgUnused` is spread with the -argument `atOtherHomes` and `$atOtherHomes` is defined as an operation variable, -there is never a variable `$atOtherHomes` used within the scope of -`fragmentArgUnused`. +This document is invalid: fragment `fragmentArgUnused` defines a fragment variable `$atOtherHomes`, but this variable is not used within this fragment. ### All Variable Usages Are Allowed From 969f692fdcc466e9b9c730a624f567361080b6cf Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Sat, 30 Mar 2024 08:28:21 +0100 Subject: [PATCH 24/27] formatting and expand examples --- spec/Section 2 -- Language.md | 21 ++++++++------- spec/Section 5 -- Validation.md | 33 ++++++++++++++++------- spec/Section 6 -- Execution.md | 47 ++++++++++++++++++++------------- 3 files changed, 62 insertions(+), 39 deletions(-) diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index 00038f469..29bb564aa 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -1216,12 +1216,13 @@ size `60`: **Variable Use Within Fragments** Variables can be used within fragments. Operation-defined variables have global -scope within a given operation. Fragment-defined variables have local scope within the -fragment definition in which they are defined. A variable used within a fragment must either -be declared in each top-level operation that transitively consumes that fragment, -or by that same fragment as a fragment variable definition. If a variable -referenced in a fragment is included by an operation where neither the fragment -nor the operation defines that variable, that operation is invalid (see +scope within a given operation. Fragment-defined variables have local scope +within the fragment definition in which they are defined. A variable used within +a fragment must either be declared in each top-level operation that transitively +consumes that fragment, or by that same fragment as a fragment variable +definition. If a variable referenced in a fragment is included by an operation +where neither the fragment nor the operation defines that variable, that +operation is invalid (see [All Variable Uses Defined](#sec-All-Variable-Uses-Defined)). ## Fragment Variable Definitions @@ -1275,10 +1276,10 @@ fragment dynamicProfilePic($size: Int!) on User { ``` The profilePic for `user` will be determined by the variables set by the -operation, while `secondUser` will always have a `profilePic` of size `10`. In this -case, the fragment `variableProfilePic` uses the operation-defined variable, -while `dynamicProfilePic` uses the value passed in via the fragment spread's -`size` argument. +operation, while `secondUser` will always have a `profilePic` of size `10`. In +this case, the fragment `variableProfilePic` uses the operation-defined +variable, while `dynamicProfilePic` uses the value passed in via the fragment +spread's `size` argument. ## Type References diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 18421899e..074ffcf63 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -424,9 +424,10 @@ FieldsInSetCanMerge(set): - Let {spreadsForName} be the set of fragment spreads with a given name in {visitedSelections}. - For each {spreadsForName} as {name} and {spreads}: - - Each entry in {spreads} must have identical sets of arguments to each other entry in {spreads}. -- Let {fieldsForName} be the set of field selections with a given response name in - {visitedSelections}. + - Each entry in {spreads} must have identical sets of arguments to each other + entry in {spreads}. +- Let {fieldsForName} be the set of field selections with a given response name + in {visitedSelections}. - Given each pair of members {fieldA} and {fieldB} in {fieldsForName}: - {SameResponseShape(fieldA, fieldB)} must be true. - If the parent types of {fieldA} and {fieldB} are equal or if either is not @@ -602,8 +603,9 @@ the fragment spread `...commandFragment(command: SIT)` and `...commandFragment(command: DOWN)` are part of the visited selections that will be merged. -If both of these spreads would have `$commandOne` or `$commandTwo` as the argument-value, -it would be allowed as we can be sure that we'd resolve identical fields. +If both of these spreads would have `$commandOne` or `$commandTwo` as the +argument-value, it would be allowed as we can be sure that we'd resolve +identical fields. ### Leaf Field Selections @@ -699,8 +701,8 @@ validation rules apply in each case. **Explanatory Text** -Every argument provided to a field or directive or fragment spread must be -defined in the set of possible arguments of that field, directive or fragment. +Every argument provided to a field or directive must be defined in the set of +possible arguments of that field or directive. For example the following are valid: @@ -712,7 +714,13 @@ fragment argOnRequiredArg on Dog { fragment argOnOptional on Dog { isHouseTrained(atOtherHomes: true) @include(if: true) } +``` +The above is also applicable to fragment-definitions and fragment-spreads, each +variable must be defined by the fragment-definition before it can be inserted as +an argument by the fragment-spread. + +```graphql example fragment withFragmentArg($command: DogCommand) on Dog { doesKnowCommand(dogCommand: $command) } @@ -738,6 +746,10 @@ and this is also invalid as the argument `dogCommand` is not defined on fragment fragment invalidFragmentArgName on Dog { ...withFragmentArg(dogCommand: SIT) } + +fragment withFragmentArg($command: DogCommand) on Dog { + doesKnowCommand(dogCommand: $command) +} ``` and this is also invalid as `unless` is not defined on `@include`. @@ -1620,8 +1632,8 @@ fragment HouseTrainedFragment on Query { } ``` -Likewise, it is valid for a fragment to define a variable with a name that -is also defined on an operation: +Likewise, it is valid for a fragment to define a variable with a name that is +also defined on an operation: ```graphql example query C($atOtherHomes: Boolean) { @@ -1989,7 +2001,8 @@ fragment fragmentArgUnused($atOtherHomes: Boolean) on Dog { } ``` -This document is invalid: fragment `fragmentArgUnused` defines a fragment variable `$atOtherHomes`, but this variable is not used within this fragment. +This document is invalid: fragment `fragmentArgUnused` defines a fragment +variable `$atOtherHomes`, but this variable is not used within this fragment. ### All Variable Usages Are Allowed diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index 6eada1599..ed50317bf 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -340,7 +340,8 @@ ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues): - For each {groupedFieldSet} as {responseKey} and {fields}: - Let {fieldName} be the name of the first entry in {fields}. Note: This value is unaffected if an alias is used. - - Let {fragmentVariableValues} be the fragment-variables value of the first entry in {fields}. + - Let {fragmentVariableValues} be the fragment-variables value of the first + entry in {fields}. - Let {fieldType} be the return type defined for the field {fieldName} of {objectType}. - If {fieldType} is defined: @@ -491,7 +492,8 @@ 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. -CollectFields(objectType, selectionSet, variableValues, visitedFragments, localVariableValues): +CollectFields(objectType, selectionSet, variableValues, visitedFragments, +localVariableValues): - If {visitedFragments} is not provided, initialize it to the empty set. - Initialize {groupedFields} to an empty ordered map of lists. @@ -499,13 +501,13 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments, localV - If {selection} provides the directive `@skip`, let {skipDirective} be that directive. - If {skipDirective}'s {if} argument is {true} or is a variable in - {localVariableValues} or {variableValues} with the value {true}, continue with the next {selection} - in {selectionSet}. + {localVariableValues} or {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 {localVariableValues} or {variableValues} with the value {true}, continue with the next - {selection} in {selectionSet}. + in {localVariableValues} or {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). @@ -525,7 +527,8 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments, localV - If {DoesFragmentTypeApply(objectType, fragmentType)} is {false}, continue with the next {selection} in {selectionSet}. - Let {localVariableValues} be the result of calling - {getArgumentValuesFromSpread(selection, fragmentDefinition, variableValues, localVariableValues)}. + {getArgumentValuesFromSpread(selection, fragmentDefinition, + variableValues, localVariableValues)}. - Let {fragmentGroupedFieldSet} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)}. @@ -564,24 +567,27 @@ DoesFragmentTypeApply(objectType, fragmentType): - If {objectType} is a possible type of {fragmentType}, return {true} otherwise return {false}. -getArgumentValuesFromSpread(fragmentSpread, fragmentDefinition, variableValues, fragmentArgumentValues): +getArgumentValuesFromSpread(fragmentSpread, fragmentDefinition, variableValues, +fragmentArgumentValues): - Let {coercedValues} be an empty unordered Map. - For each {variableDefinition} in {fragmentDefinition}: - Let {variableName} be the name of {variableDefinition}. - Let {variableType} be the type of {variableDefinition}. - Let {defaultValue} be the default value for {variableDefinition}. - - Let {argumentNode} be the node provided in the fragment-spread for {variableName} + - Let {argumentNode} be the node provided in the fragment-spread for + {variableName} - If {argumentNode} isn't present or is null - If {defaultValue} exists - Add an entry to {coercedValues} named {argumentName} with the value {defaultValue}. - If {variableType} is non-nullable raise a field-error - - Let {hasValue} be {true} if {fragmentArgumentValues} or {variableValues} provides a value for the name - {variableName}. - - If {variableType} is non-nullable and {hasValue} is {false} raise a field-error - - Add an entry to {coercedValues} named {argumentName} with the value - found in {variableValues} or {fragmentArgumentValues}. + - Let {hasValue} be {true} if {fragmentArgumentValues} or {variableValues} + provides a value for the name {variableName}. + - If {variableType} is non-nullable and {hasValue} is {false} raise a + field-error + - Add an entry to {coercedValues} named {argumentName} with the value found in + {variableValues} or {fragmentArgumentValues}. - Return {coercedValues}. Note: The steps in {CollectFields()} evaluating the `@skip` and `@include` @@ -595,7 +601,8 @@ coerces any provided argument values, then resolves a value for the field, and finally completes that value either by recursively executing another selection set or coercing a scalar value. -ExecuteField(objectType, objectValue, fieldType, fields, variableValues, fragmentVariableValues): +ExecuteField(objectType, objectValue, fieldType, fields, variableValues, +fragmentVariableValues): - Let {field} be the first entry in {fields}. - Let {fieldName} be the field name of {field}. @@ -615,7 +622,8 @@ the type system to have a specific input type. At each argument position in an operation may be a literal {Value}, or a {Variable} to be provided at runtime. -CoerceFieldArgumentValues(objectType, field, variableValues, fragmentVariableValues): +CoerceFieldArgumentValues(objectType, field, variableValues, +fragmentVariableValues): - Let {argumentValues} be the argument values provided in {field}. - Let {fieldName} be the name of {field}. @@ -624,7 +632,8 @@ CoerceFieldArgumentValues(objectType, field, variableValues, fragmentVariableVal - Return {CoerceArgumentValues(argumentDefinitions, argumentValues, variableValues, fragmentVariableValues)} -CoerceArgumentValues(argumentDefinitions, argumentValues, variableValues, fragmentVariableValues): +CoerceArgumentValues(argumentDefinitions, argumentValues, variableValues, +fragmentVariableValues): - For each {argumentDefinition} in {argumentDefinitions}: - Let {argumentName} be the name of {argumentDefinition}. @@ -636,8 +645,8 @@ CoerceArgumentValues(argumentDefinitions, argumentValues, variableValues, fragme {argumentName}. - If {argumentValue} is a {Variable}: - Let {variableName} be the name of {argumentValue}. - - Let {hasValue} be {true} if {fragmentVariableValues} provides a value for the name - {variableName}. + - Let {hasValue} be {true} if {fragmentVariableValues} provides a value for + the name {variableName}. - Let {value} be the value provided in {fragmentVariableValues} for the name {variableName}. - Let {hasValue} be {true} if {variableValues} provides a value for the name From 2e0c5ad6d38adb62bb20cc9f96e2fd2cb804097b Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Sat, 30 Mar 2024 08:30:46 +0100 Subject: [PATCH 25/27] add in undefined fragment --- spec/Section 5 -- Validation.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 074ffcf63..81a31d7d8 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -1643,6 +1643,12 @@ query C($atOtherHomes: Boolean) { } } +fragment HouseTrainedFragment on Query { + dog { + isHouseTrained(atOtherHomes: $atOtherHomes) + } +} + fragment HouseTrainedDog($atOtherHomes: Boolean) on Dog { isHouseTrained(atOtherHomes: $atOtherHomes) } From 51b9ef7da0b9fd652aecf6239581e0dcbfa669f8 Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Sat, 30 Mar 2024 08:32:18 +0100 Subject: [PATCH 26/27] unset instead of null --- spec/Section 5 -- Validation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 81a31d7d8..8252d9d86 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -1658,8 +1658,8 @@ Fragment-defined variables are scoped locally to the fragment that defines them, and override any operation-defined variable values, so there is never ambiguity about which value to use. In this case, the value of the argument `atOtherHomes` within `HouseTrainedFragment` will be the operation-set value, and within -`HouseTrainedDog` will resolve to `null`, as the argument is not set by the -fragment spread in the query `C`. +`HouseTrainedDog` will default to being unset (unless a default-value applies), +as the argument is not set by the fragment spread in the query `C`. ### Variables Are Input Types From 32303848e8c11247774280c29c7cbcb39b2fb55a Mon Sep 17 00:00:00 2001 From: Jovi De Croock Date: Sat, 30 Mar 2024 08:34:00 +0100 Subject: [PATCH 27/27] shorten --- spec/Section 5 -- Validation.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 8252d9d86..9bb7f0077 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -419,8 +419,7 @@ fragment directFieldSelectionOnUnion on CatOrDog { FieldsInSetCanMerge(set): - Let {visitedSelections} be the selections in {set} including visiting - fragments and inline fragments and applying any supplied fragment spread - arguments. + fields, fragment-spreads and inline fragments. - Let {spreadsForName} be the set of fragment spreads with a given name in {visitedSelections}. - For each {spreadsForName} as {name} and {spreads}: