diff --git a/spec/Appendix C -- Grammar Summary.md b/spec/Appendix C -- Grammar Summary.md index cc464a4a6..7d9d63b24 100644 --- a/spec/Appendix C -- Grammar Summary.md +++ b/spec/Appendix C -- Grammar Summary.md @@ -172,12 +172,12 @@ Arguments[Const] : ( Argument[?Const]+ ) Argument[Const] : Name : Value[?Const] -FragmentSpread : ... FragmentName Directives? +FragmentSpread : ... FragmentName Arguments? Directives? InlineFragment : ... TypeCondition? Directives? SelectionSet -FragmentDefinition : Description? fragment FragmentName TypeCondition -Directives? SelectionSet +FragmentDefinition : Description? 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 94b99f20d..f278ca71c 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -578,10 +578,10 @@ which returns the result: ## Fragments -FragmentSpread : ... FragmentName Directives? +FragmentSpread : ... FragmentName Arguments? Directives? -FragmentDefinition : Description? fragment FragmentName TypeCondition -Directives? SelectionSet +FragmentDefinition : Description? fragment FragmentName VariablesDefinition? +TypeCondition Directives? SelectionSet FragmentName : Name but not `on` @@ -1251,10 +1251,15 @@ avoiding costly string building in clients at runtime. If not defined as constant (for example, in {DefaultValue}), a {Variable} can be supplied for an input value. -Variables must be defined at the top of an operation and are in scope throughout -the execution of that operation. Values for those variables are provided to a -GraphQL service as part of a request so they may be substituted in during -execution. +:: An _operation variable_ is a variable defined at the top of an operation. An +operation variable is in scope throughout the execution of that operation. +Values for operation variables are provided to a GraphQL service as part of a +request so they may be substituted in during execution. + +:: A _fragment variable_ is a variable defined on a fragment. A fragment +variable is locally scoped, it may only be referenced within that fragment +(non-transitively). Values for fragment variables are provided to fragment +spreads. In this example, we want to fetch a profile picture size based on the size of a particular device: @@ -1283,13 +1288,73 @@ 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. An _operation variable_ has global scope +within that operation, including within any fragments that operation +transitively consumes. A variable used within a fragment must either be defined +by that fragment, or must be declared in each top-level operation that +transitively consumes that fragment. If a variable referenced in a fragment is +defined in both the fragment and the operation, the fragment definition will be +used. If a variable is referenced in a fragment and is not defined by that +fragment, then any operation that transitively references the fragment and does +not define that variable is invalid (see [All Variable Uses Defined](#sec-All-Variable-Uses-Defined)). +## Fragment Variable Definitions + +Fragments may define locally scoped variables. This allows 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 userAndFriends { + 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 variable_ is scoped to the fragment that defines it. A fragment +variable may shadow an _operation variable_. + +```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) +} +``` + +In the example above, 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 value of the _operation variable_, while `dynamicProfilePic` uses the value +of the _fragment variable_ passed in via the fragment spread's `size` argument. + ## Type References Type : diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index c48a6ba4a..47af3969e 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -556,6 +556,11 @@ fragment directFieldSelectionOnUnion on CatOrDog { FieldsInSetCanMerge(set): +- Let {spreadsForFragment} be the set of fragment spreads for a given named + fragment in {set} including visiting fragments and inline fragments. +- Given each pair of distinct members {spreadA} and {spreadB} in + {spreadsForFragment}: + - {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. - Given each pair of distinct members {fieldA} and {fieldB} in {fieldsForName}: @@ -568,6 +573,13 @@ FieldsInSetCanMerge(set): the selection set of {fieldB}. - {FieldsInSetCanMerge(mergedSet)} must be true. +Note: When checking if two fragment spreads or two field selections have +"identical sets of arguments", it is necessary to check both the name of any +variables referenced and also where the variable is defined. For example, if +each selection references the variable `$size`, then `$size` must either be an +operation variable for both references, or must refer to the same fragment +variable definition for both references. + SameResponseShape(fieldA, fieldB): - Let {typeA} be the return type of {fieldA}. @@ -713,6 +725,53 @@ fragment conflictingDifferingResponses on Pet { } ``` +Fragment spread arguments can also cause fields to fail to merge. + +In the following, the arguments to {commandFragment} reference different +variables and so cannot merge: + +```graphql counter-example +fragment commandFragment($command: DogCommand!) on Dog { + doesKnowCommand(dogCommand: $command) +} + +fragment potentiallyConflictingArguments( + $commandOne: DogCommand! + $commandTwo: DogCommand! +) on Dog { + ...commandFragment(command: $commandOne) + ...commandFragment(command: $commandTwo) +} + +query { + pet { + ...potentiallyConflictingArguments(commandOne: SIT, commandTwo: DOWN) + } +} +``` + +Though the fragment is referenced consistently as +`...commandFragment(command: $command)` in the following, the position in which +the ${command} variable is defined for each reference differs, and thus the +arguments do not match: + +```graphql counter-example +fragment commandFragment($command: DogCommand!) on Dog { + doesKnowCommand(dogCommand: $command) +} + +fragment otherFragment(command: DogCommand!) on Dog { + ...commandFragment(command: $command) +} + +query KnowsCommand($command: DogCommand!) { + pet { + ...commandFragment(command: $command) + ...otherFragment(command: DOWN) + } +} +``` + ### Leaf Field Selections **Formal Specification** @@ -790,23 +849,34 @@ 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 **Formal Specification** -- For each {argument} in the document: +- For each field or directive {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}. + field or directive named {argumentName}. - {argumentDefinition} must exist. +- For each fragment argument {fragmentArgument} in the document: + - Let {variableName} be the Name of {fragmentArgument}. + - Let {namedSpread} be the named spread to which {fragmentArgument} is + applied. + - Let {fragment} be the target of {namedSpread}. + - If no such {fragment} exists, continue with the next {fragmentArgument}. + - Let {variableDefinition} be the variable definition named {variableName} + provided by {fragment}. + - {variableDefinition} 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. +possible arguments of that field or directive. Every argument provided to a +named fragment spread must be defined in the set of possible variables of that +named fragment. For example the following are valid: @@ -818,9 +888,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 the argument `command` is not defined on +`Dog.doesKnowCommand`: ```graphql counter-example fragment invalidArgName on Dog { @@ -828,7 +907,7 @@ fragment invalidArgName on Dog { } ``` -and this is also invalid as `unless` is not defined on `@include`. +This is also invalid as `unless` is not defined on `@include`: ```graphql counter-example fragment invalidArgName on Dog { @@ -836,6 +915,19 @@ fragment invalidArgName on Dog { } ``` +Since the argument `dogCommand` is not defined on fragment `withFragmentArg`, +this is also invalid: + +```graphql counter-example +fragment invalidFragmentArgName on Dog { + ...withFragmentArg(dogCommand: SIT) +} + +fragment withFragmentArg($command: DogCommand) on Dog { + doesKnowCommand(dogCommand: $command) +} +``` + In order to explore more complicated argument examples, let's add the following to our type system: @@ -870,9 +962,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, named 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** @@ -884,23 +976,24 @@ invalid. ### Required Arguments -- For each Field or Directive in the document: - - Let {arguments} be the arguments provided by the Field or Directive. - - Let {argumentDefinitions} be the set of argument definitions of that Field - or Directive. - - For each {argumentDefinition} in {argumentDefinitions}: - - Let {type} be the expected type of {argumentDefinition}. - - Let {defaultValue} be the default value of {argumentDefinition}. +- For each Field, Directive, or Named Fragment Spread in the document: + - Let {arguments} be the arguments provided to the Field, Directive, or Named + Fragment Spread. + - Let {inputDefinitions} be the set of argument definitions of that Field or + Directive, or the variable definitions of the referenced Fragment. + - For each {inputDefinition} in {inputDefinitions}: + - Let {type} be the expected type of {inputDefinition}. + - Let {defaultValue} be the default value of {inputDefinition}. - If {type} is Non-Null and {defaultValue} does not exist: - - Let {argumentName} be the name of {argumentDefinition}. - - Let {argument} be the argument in {arguments} named {argumentName}. + - Let {inputName} be the name of {inputDefinition}. + - Let {argument} be the argument in {arguments} named {inputName}. - {argument} must exist. - Let {value} be the value of {argument}. - {value} must not be the {null} literal. **Explanatory Text** -Arguments can be required. An argument is required if the argument type is +Arguments can be required. An argument is required if the expected type is non-null and does not have a default value. Otherwise, the argument is optional. For example the following are valid: @@ -942,6 +1035,30 @@ fragment missingRequiredArg on Arguments { } ``` +Fragments can also have required arguments, making this invalid: + +```graphql counter-example +fragment fragmentWithNonNullVariable($arg: Boolean!) on Arguments { + nonNullBooleanArgField(nonNullBooleanArg: $arg) +} + +fragment goodFragmentDefault on Arguments { + ...fragmentWithNonNullVariable +} +``` + +but fragment variables can have defaults, making the argument optional: + +```graphql example +fragment fragmentWithDefault($arg: Boolean! = true) on Arguments { + nonNullBooleanArgField(nonNullBooleanArg: $arg) +} + +fragment goodFragmentDefault on Arguments { + ...fragmentWithDefault +} +``` + ## Fragments ### Fragment Declarations @@ -1701,18 +1818,20 @@ query ($foo: Boolean = true, $bar: Boolean = false) { **Formal Specification** -- For every {operation} in the document: - - For every {variable} defined on {operation}: +- Let {operationsAndFragments} be the set of all operation and fragment + definitions in the document. +- For each {operationOrFragment} in {operationsAndFragments}: + - 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) { @@ -1727,26 +1846,57 @@ two operations reference the same fragment, it might actually be necessary: ```graphql example query A($atOtherHomes: Boolean) { - ...HouseTrainedFragment + ...houseTrained } query B($atOtherHomes: Boolean) { - ...HouseTrainedFragment + ...houseTrained +} + +fragment houseTrained on Query { + dog { + isHouseTrained(atOtherHomes: $atOtherHomes) + } +} +``` + +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) { + ...houseTrained + aDog: dog { + ...houseTrainedWithArgument + } } -fragment HouseTrainedFragment on Query { +fragment houseTrained on Query { dog { isHouseTrained(atOtherHomes: $atOtherHomes) } } + +fragment houseTrainedWithArgument($atOtherHomes: Boolean) on Dog { + isHouseTrained(atOtherHomes: $atOtherHomes) +} ``` +A _fragment variable_ is scoped locally to the fragment that defines it, and +overrides the _operation variable_ of the same name, if any, so there is never +ambiguity about which value to use. In the above example, the value of the +argument {atOtherHomes} within {houseTrained} will be the _operation variable_'s +value. Within {houseTrainedWithArgument} the argument {atOtherHomes} will not +provide a value, as no value is supplied by the fragment spread in query `C`. + ### Variables Are Input Types **Formal Specification** -- For every {operation} in a {document}: - - For every {variable} on each {operation}: +- Let {operationsAndFragments} be the set of all operation and fragment + definitions in the document. +- For each {operationOrFragment} in {operationsAndFragments}: + - For every {variable} defined on {operationOrFragment}: - Let {variableType} be the type of {variable}. - {IsInputType(variableType)} must be {true}. @@ -1814,13 +1964,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 variable list, {operation}'s variable list, or both. **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 +An _operation variable_ is scoped on a per-operation basis, while a _fragment +variable_ is scoped locally to the fragment. Any variable used within the +context of an operation must be defined either at the top level of that +operation or on the fragment that uses that variable. For example: @@ -1847,9 +1998,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 also those +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: @@ -1954,7 +2106,12 @@ 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 a variable. +- 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 transitively + within the fragment's selection set excluding traversal of named fragment + spreads. **Explanatory Text** @@ -2006,6 +2163,28 @@ fragment isHouseTrainedWithoutVariableFragment on Dog { } ``` +A _fragment variable_ can shadow an _operation variable_: fragments that define +a variable cannot use the operation variable of the same name (if any). + +As such, it would be invalid if the operation defined a variable and variables +of that name were used exclusively inside fragments that define a variable with +the same name: + +```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 variable, the _operation variable_ ${atOtherHomes} is never used. + All operations in a document must use all of their variables. As a result, the following document does not validate. @@ -2031,6 +2210,24 @@ fragment isHouseTrainedFragment on Dog { This document is not valid because {queryWithExtraVar} defines an extraneous variable. +Fragment variables must also be used within their definitions. 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: fragment {fragmentArgUnused} defines a fragment +variable ${atOtherHomes}, but this variable is not used within this fragment. + ### All Variable Usages Are Allowed **Formal Specification** @@ -2039,8 +2236,11 @@ 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 that {VariableDefinition}. + - 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 5bde7a6c1..e90ce4b10 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -101,10 +101,8 @@ CoerceVariableValues(schema, operation, variableValues): - Let {coercedValues} be an empty unordered Map. - Let {variablesDefinition} be the variables defined by {operation}. - For each {variableDefinition} in {variablesDefinition}: - - Let {variableName} be the name of {variableDefinition}. - - Let {variableType} be the expected type of {variableDefinition}. - - Assert: {IsInputType(variableType)} must be {true}. - - Let {defaultValue} be the default value for {variableDefinition}. + - Let {variableName}, {variableType}, and {defaultValue} be the result of + {GetVariableSignature(variableDefinition)}. - Let {hasValue} be {true} if {variableValues} provides a value for the name {variableName}. - Let {value} be the value provided in {variableValues} for the name @@ -131,6 +129,14 @@ CoerceVariableValues(schema, operation, variableValues): Note: This algorithm is very similar to {CoerceArgumentValues()}. +GetVariableSignature(variableDefinition): + +- Let {variableName} be the name of {variableDefinition}. +- Let {variableType} be the expected type of {variableDefinition}. +- Assert: {IsInputType(variableType)} must be {true}. +- Let {defaultValue} be the default value for {variableDefinition}. +- Return {variableName}, {variableType}, and {defaultValue}. + ## Executing Operations The type system, as described in the "Type System" section of the spec, must @@ -281,9 +287,10 @@ CreateSourceEventStream(subscription, schema, variableValues, initialValue): - If {collectedFieldsMap} does not have exactly one entry, raise a _request error_. - Let {fields} be the value of the first entry in {collectedFieldsMap}. -- Let {fieldName} be the name of the first entry in {fields}. Note: This value - is unaffected if an alias is used. -- Let {field} be the first entry in {fields}. +- Let {fieldInfo} be the first entry in {fields}. +- Let {field} be the value of the {field} property in {fieldInfo}. +- Let {fieldName} be the name of {field}. Note: This value is unaffected if an + alias is used. - Let {argumentValues} be the result of {CoerceArgumentValues(subscriptionType, field, variableValues)}. - Let {sourceStream} be the result of running @@ -439,7 +446,8 @@ The depth-first-search order of each _field set_ 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, +fragmentVariables): - If {visitedFragments} is not provided, initialize it to the empty set. - Initialize {collectedFieldsMap} to an empty ordered map of ordered sets. @@ -460,7 +468,9 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - Let {fieldsForResponseName} be the _field set_ value in {collectedFieldsMap} for the key {responseName}; otherwise create the entry with an empty ordered set. - - Add {selection} to the {fieldsForResponseName}. + - Let {fieldDetails} be a new unordered map containing {fragmentVariables}. + - Set the entry for {field} on {fieldDetails} to {selection}. + - Add {fieldDetails} to the {fieldsForResponseName}. - If {selection} is a {FragmentSpread}: - Let {fragmentSpreadName} be the name of {selection}. - If {fragmentSpreadName} is in {visitedFragments}, continue with the next @@ -473,10 +483,20 @@ 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 {variableDefinitions} be the variable definitions for {fragment}. + - Initialize {signatures} to an empty list. + - For each {variableDefinition} of {variableDefinitions}: + - Append the result of {GetVariableSignature(variableDefinition)} to + {signatures}. + - Let {argumentValues} be the arguments provided in {selection}. + - Let {values} be the result of {CoerceArgumentValues(selection, + variableDefinitions, argumentValues, variableValues, fragmentVariables)}. + - Let {newFragmentVariables} be an unordered map containing {signatures} and + {values}. - Let {fragmentSelectionSet} be the top-level selection set of {fragment}. - Let {fragmentCollectedFieldsMap} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, - visitedFragments)}. + visitedFragments, newFragmentVariables)}. - For each {responseName} and {fragmentFields} in {fragmentCollectedFieldsMap}: - Let {fieldsForResponseName} be the _field set_ value in @@ -491,7 +511,7 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): - Let {fragmentSelectionSet} be the top-level selection set of {selection}. - Let {fragmentCollectedFieldsMap} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, - visitedFragments)}. + visitedFragments, fragmentVariables)}. - For each {responseName} and {fragmentFields} in {fragmentCollectedFieldsMap}: - Let {fieldsForResponseName} be the _field set_ value in @@ -555,11 +575,11 @@ CollectSubfields(objectType, fields, variableValues): - If {fieldSelectionSet} is null or empty, continue to the next field. - Let {fieldCollectedFieldsMap} be the result of {CollectFields(objectType, fieldSelectionSet, variableValues)}. - - For each {responseName} and {subfields} in {fieldCollectedFieldsMap}: + - For each {responseName} and {subfieldInfos} in {fieldCollectedFieldsMap}: - Let {fieldsForResponseName} be the _field set_ value in {collectedFieldsMap} for the key {responseName}; otherwise create the entry with an empty ordered set. - - Add each fields from {subfields} to {fieldsForResponseName}. + - Add each fieldInfo from {subfieldInfos} to {fieldsForResponseName}. - Return {collectedFieldsMap}. Note: All the {fields} passed to {CollectSubfields()} share the same _response @@ -578,14 +598,18 @@ ExecuteCollectedFields(collectedFieldsMap, objectType, objectValue, variableValues): - Initialize {resultMap} to an empty ordered map. -- For each {responseName} and {fields} in {collectedFieldsMap}: - - Let {fieldName} be the name of the first entry in {fields}. Note: This value - is unaffected if an alias is used. +- For each {responseName} and {fieldInfos} in {collectedFieldsMap}: + - Let {fieldInfo} be the first entry in {fieldInfos}. + - Let {field} be the field property of {fieldInfo}. + - Let {fieldName} be the name of {field}. Note: This value is unaffected if an + alias is used. + - Let {fragmentVariableValues} be the fragmentVariables property of + {fieldInfo}. - 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)}. + fieldInfos, variableValues, fragmentVariableValues)}. - Set {responseValue} as the value for {responseName} in {resultMap}. - Return {resultMap}. @@ -719,33 +743,46 @@ first 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, fieldDetailsList, +variableValues, fragmentVariables): -- Let {field} be the first entry in {fields}. +- Let {fieldDetails} be the first entry in {fieldDetailsList}. +- Let {field} and {fragmentVariables} be the corresponding entries on + {fieldDetails}. - Let {fieldName} be the field name of {field}. -- Let {argumentValues} be the result of {CoerceArgumentValues(objectType, field, - variableValues)}. +- Let {argumentDefinitions} be the arguments defined by {objectType} for the + field named {fieldName}. +- Let {argumentValues} be the result of {CoerceArgumentValues(field, + argumentDefinitions, variableValues, fragmentVariables)}. - Let {resolvedValue} be {ResolveFieldValue(objectType, objectValue, fieldName, - argumentValues)}. -- Return the result of {CompleteValue(fieldType, fields, resolvedValue, - variableValues)}. + argumentValues)}. Return the result of {CompleteValue(fieldType, + fieldDetailsList, resolvedValue, variableValues)}. -### Coercing Field Arguments +### Coercing Arguments -Fields may include arguments which are provided to the underlying runtime in -order to correctly produce a value. These arguments are defined by the field in -the type system to have a specific input type. +Fields, directives, and fragment spreads may include arguments which are +provided to the underlying runtime in order to correctly produce a value. For +fields and directives, these arguments are defined by the field or directive in +the type system to have a specific input type; for fragment spreads, the +fragment definition within the document specifies the 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, +fragmentVariableValues): -- 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, fragmentVariableValues)} + +CoerceArgumentValues(node, argumentDefinitions, argumentValues, variableValues, +fragmentVariableValues): + +- Let {coercedValues} be an empty unordered Map. - For each {argumentDefinition} in {argumentDefinitions}: - Let {argumentName} be the name of {argumentDefinition}. - Let {argumentType} be the expected type of {argumentDefinition}. @@ -754,13 +791,15 @@ CoerceArgumentValues(objectType, field, variableValues): {argumentName}. - If {argumentValue} is a {Variable}: - Let {variableName} be the name of {argumentValue}. - - If {variableValues} provides a value for the name {variableName}: - - Let {hasValue} be {true}. - - Let {value} be the value provided in {variableValues} for the name - {variableName}. - - Otherwise if {argumentValues} provides a value for the name {argumentName}. - - Let {hasValue} be {true}. - - Let {value} be {argumentValue}. + - Let {signatures} and {values} be the corresponding entries on + {fragmentVariables}. + - Let {scopedVariableValues} be {values} if an entry in {signatures} exists + for {variableName}; otherwise, let it be {variableValues}. + - Let {hasValue} be {true} if {scopedVariableValues} provides a value for + the name {variableName}. + - Let {value} be the value provided in {scopedVariableValues} for the name + {variableName}. + - Otherwise, let {value} be {argumentValue}. - If {hasValue} is not {true} and {defaultValue} exists (including {null}): - Let {coercedDefaultValue} be the result of coercing {defaultValue} according to the input coercion rules of {argumentType}. @@ -825,12 +864,12 @@ the expected return type. If the return type is another Object type, then the field execution process continues recursively by collecting and executing subfields. -CompleteValue(fieldType, fields, result, variableValues): +CompleteValue(fieldType, fieldDetailsList, result, variableValues): - If the {fieldType} is a Non-Null type: - Let {innerType} be the inner type of {fieldType}. - Let {completedResult} be the result of calling {CompleteValue(innerType, - fields, result, variableValues)}. + fieldDetailsList, result, variableValues)}. - If {completedResult} is {null}, raise an _execution error_. - Return {completedResult}. - If {result} is {null} (or another internal value similar to {null} such as @@ -839,8 +878,8 @@ CompleteValue(fieldType, fields, result, variableValues): - If {result} is not a collection of values, raise an _execution error_. - Let {innerType} be the inner type of {fieldType}. - Return a list where each list item is the result of calling - {CompleteValue(innerType, fields, resultItem, variableValues)}, where - {resultItem} is each item in {result}. + {CompleteValue(innerType, fieldDetailsList, resultItem, variableValues)}, + where {resultItem} is each item in {result}. - If {fieldType} is a Scalar or Enum type: - Return the result of {CoerceResult(fieldType, result)}. - If {fieldType} is an Object, Interface, or Union type: @@ -849,7 +888,7 @@ CompleteValue(fieldType, fields, result, variableValues): - Otherwise if {fieldType} is an Interface or Union type. - Let {objectType} be {ResolveAbstractType(fieldType, result)}. - Let {collectedFieldsMap} be the result of calling - {CollectSubfields(objectType, fields, variableValues)}. + {CollectSubfields(objectType, fieldDetailsList, variableValues)}. - Return the result of evaluating {ExecuteCollectedFields(collectedFieldsMap, objectType, result, variableValues)} _normally_ (allowing for parallelization).