From 9e68777f0ee50809de1f10c55fdd909e618e7fe1 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Mon, 6 Jul 2015 16:56:03 -0700 Subject: [PATCH] Directives improvements Here is spec language mirroring the changes in https://github.com/graphql/graphql-js/pull/31. Changed: * Directive grammar changed to `@ Name Arguments?` * "Field Arguments" changed to "Arguments" almost everywhere to be more generic. * Argument validation rules made more generic to handle directives cases. * Directive validation simplified. * Directive explaination in Language now matches current state. * @include and @skip documented in Type System --- Section 2 -- Language.md | 89 ++++++++++++++++------- Section 3 -- Type System.md | 54 ++++++++++---- Section 4 -- Introspection.md | 2 +- Section 5 -- Validation.md | 130 +++++++++++++--------------------- Section 6 -- Execution.md | 6 ++ Section 8 -- Grammar.md | 4 +- 6 files changed, 162 insertions(+), 123 deletions(-) diff --git a/Section 2 -- Language.md b/Section 2 -- Language.md index 3af4aaeee..c444cb234 100644 --- a/Section 2 -- Language.md +++ b/Section 2 -- Language.md @@ -86,11 +86,12 @@ query getZuck { } ``` -## Field Arguments +## Arguments -Fields may take arguments. These often map directly to function arguments -within the GraphQL server implementation. We already saw arguments used -in the global field above. +Fields and directives may take arguments. + +These often map directly to function arguments within the GraphQL server +implementation. We already saw arguments used in the global field above. In this example, we want to query a user's profile picture of a specific size: @@ -119,7 +120,7 @@ Many arguments can exist for a given field: **Arguments are unordered** -Field arguments may be provided in any syntactic order and maintain identical +Arguments may be provided in any syntactic order and maintain identical semantic meaning. These two queries are semantically identical: @@ -195,7 +196,7 @@ the field's name otherwise. ## Input Values -Both field arguments and directives accept input values. Input values can be +Field and directive arguments accept input values. Input values can be specified as a variable or represented inline as literals. Input values can be scalars, enumerations, or input objects. List and inputs objects may also contain variables. @@ -270,26 +271,6 @@ could run this query and request profilePic of size 60 with: } ``` -## Directives - -In some cases, you need to provide options to alter GraphQL's execution -behavior in ways field arguments will not suffice, such as conditionally -skipping a field. Directives provide this with a `@name` and can be -specified to be used without an argument or with a value argument. - -Directives can be used to conditionally include fields in a query based -on a provided boolean value. In this contrived example experimentalField -will be queried and controlField will not. - -```graphql -query myQuery($someTest: Boolean) { - experimentalField @if: $someTest, - controlField @unless: $someTest -} -``` - -As future versions of GraphQL adopts new configurable execution capabilities, -they may be exposed via directives. ## Fragments @@ -446,3 +427,59 @@ query InlineFragmentTyping { } ``` + +## Directives + +In some cases, you need to provide options to alter GraphQL's execution +behavior in ways field arguments will not suffice, such as conditionally +including or skipping a field. Directives provide this by describing additional information to the executor. + +Directives have a name along with a list of arguments which may accept values +of any input type. + +Directives can be used to describe additional information for fields, fragments, +and operations. + +As future versions of GraphQL adopts new configurable execution capabilities, +they may be exposed via directives. + +### Fragment Directives + +Fragments may include directives to alter their behavior. At runtime, the directives provided on a fragment spread override those described on the +definition. + +For example, the following query: + +```graphql +query HasConditionalFragment($condition: Boolean) { + ...MaybeFragment @include(if: $condition) +} + +fragment MaybeFragment on Query { + me { + name + } +} +``` + +Will have identical runtime behavior as + +```graphql +query HasConditionalFragment($condition: Boolean) { + ...MaybeFragment +} + +fragment MaybeFragment on Query @include(if: $condition) { + me { + name + } +} +``` + +FragmentSpreadDirectives(fragmentSpread) : + * Let {directives} be the set of directives on {fragmentSpread} + * Let {fragmentDefinition} be the FragmentDefinition in the document named {fragmentSpread} refers to. + * For each {directive} in directives on {fragmentDefinition} + * If {directives} does not contain a directive named {directive}. + * Add {directive} into {directives} + * Return {directives} diff --git a/Section 3 -- Type System.md b/Section 3 -- Type System.md index 5d85a87d7..ba23690a0 100644 --- a/Section 3 -- Type System.md +++ b/Section 3 -- Type System.md @@ -100,7 +100,7 @@ Scalar type. **Input Coercion** -If a GraphQL server expects a scalar type as input to a field argument, coercion +If a GraphQL server expects a scalar type as input to an argument, coercion is observable and the rules must be well defined. If an input value does not match a coercion rule, a query error must be raised. @@ -354,9 +354,9 @@ Objects are never valid inputs. #### Object Field Arguments Object fields are conceptually functions which yield values. Occasionally object -fields can accept arguments to further specify the return value. Field arguments -are defined as a list of all possible argument names and their expected input -types. +fields can accept arguments to further specify the return value. Object field +arguments are defined as a list of all possible argument names and their +expected input types. For example, a `Person` type with a `picture` field could accept an argument to determine what size of an image to return. @@ -389,9 +389,7 @@ May yield the result: } ``` -The type of a field argument can be a Scalar, as it was in this example, or an -Enum. It can also be an Input Object, covered later in this document, or it can -be any wrapping type whose underlying base type is one of those three. +The type of an object field argument can be any Input type. #### Object Field deprecation @@ -723,14 +721,42 @@ case), the client can just pass that value rather than constructing the list. ## Directives -A GraphQL schema includes a list of supported directives, each of which has -a name. Directives can apply to operations, fragments, or fields; each directive -indicates which of those it applies to. +A GraphQL schema includes a list of the directives the execution +engine supports. + +GraphQL implementations should provide the `@skip` and `@include` directives. + +### @skip + +The `@skip` directive may be provided for fields or fragments, and allows +for conditional exclusion during execution as described by the if argument. + +In this example `experimentalField` will be queried only if the `$someTest` is +provided a `false` value. + +```graphql +query myQuery($someTest: Boolean) { + experimentalField @skip(if: $someTest) +} +``` + +### @include + +The `@include` directive may be provided for fields or fragments, and allows +for conditional inclusion during execution as described by the if argument. + +In this example `experimentalField` will be queried only if the `$someTest` is +provided a `true` value. + +```graphql +query myQuery($someTest: Boolean) { + experimentalField @include(if: $someTest) +} +``` + +The `@skip` directive has precidence over the `@include` directive should both +be provided in the same context. -Directives can optionally take an argument. The type of the argument to -a directive has the same restrictions as the type of an argument to a field. It -can be a Scalar, an Enum, an Input Object, or any wrapping type whose underlying -base type is one of those three. ## Starting types diff --git a/Section 4 -- Introspection.md b/Section 4 -- Introspection.md index b1f72c0ed..608a1486d 100644 --- a/Section 4 -- Introspection.md +++ b/Section 4 -- Introspection.md @@ -170,7 +170,7 @@ enum __TypeKind { type __Directive { name: String! description: String - type: __Type + args: [__InputValue!]! onOperation: Boolean! onFragment: Boolean! onField: Boolean! diff --git a/Section 5 -- Validation.md b/Section 5 -- Validation.md index 95d56588b..8e8d7c212 100644 --- a/Section 5 -- Validation.md +++ b/Section 5 -- Validation.md @@ -119,8 +119,8 @@ fragment definedOnImplementorsQueriedOnUnion on CatOrDog { * Let {setForKey} be the set of selections with a given response key in {set} * All members of {setForKey} must: * Have identical target fields - * Have identical sets of arguments name-value pairs. - * Have identical sets of directive name-value pairs. + * Have identical sets of arguments. + * Have identical sets of directives. ** Explanatory Text ** @@ -157,9 +157,8 @@ fragment conflictingBecauseAlias on Dog { ``` -Identical field arguments are also merged if they have -identical arguments. Both values and variables can be -correctly merged. +Identical arguments are also merged if they have identical arguments. Both +values and variables can be correctly merged. For example the following correctly merge: @@ -204,20 +203,18 @@ The following is valid: ```graphql fragment mergeSameFieldsWithSameDirectives on Dog { - name @if:true - name @if:true + name @include(if: true) + name @include(if: true) } ``` and the following is invalid: ```!graphql - fragment conflictingDirectiveArgs on Dog { - name @if: true - name @unless: false + name @include(if: true) + name @include(if: false) } - ``` ### Leaf Field Selections @@ -288,23 +285,22 @@ query directQueryOnUnionWithoutSubFields ## Arguments +Arguments are provided to both fields and directives. The following validation +rules apply in both cases. + ### Argument Names ** Formal Specification ** - * For each {selection} in the document - * Let {arguments} be the set of argument provided to the {selection} - * Let {targetField} be the target field of a given {selection} - * Let {argumentDefinitions} be the set of argument definitions of {targetField} - * Each {argumentName} in {arguments} must have a corresponding argument definition - in the {targetField} with the same name + * 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}. + * {argumentDefinition} must exist. ** Explanatory Text ** -Field selections may take arguments. Each field selection corresponds to a -field definition on the enclosing type, which specifies a set of possible -arguments. Every argument provided to the selection must be defined in the set -of possible arguments. +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: @@ -314,18 +310,24 @@ fragment argOnRequiredArg on Dog { } fragment argOnOptional on Dog { - isHousetrained(atOtherHomes: true) + isHousetrained(atOtherHomes: true) @include(if: true) } ``` -and the following is invalid since command is not defined on DogCommand: +the following is invalid since `command` is not defined on `DogCommand`. ```!graphql - fragment invalidArgName on Dog { doesKnowCommand(command: CLEAN_UP_HOUSE) } +``` +and this is also invalid as `unless` is not defined on `@include`. + +```!graphql +fragment invalidArgName on Dog { + isHousetrained(atOtherHomes: true) @include(unless: false) +} ``` In order to explore more complicated argument examples, let's add the following @@ -359,22 +361,18 @@ fragment multipleArgsReverseOrder on Arguments { ** Formal Specification ** - * For each {selection} in the document - * Let {arguments} be the set of argument provided to the {selection} - * Let {targetField} be the target field of a given {selection} - * Let {argumentDefinitions} be the set of argument definitions of {targetField} - * For each {literalArgument} of all {arguments} with literal for values. - * The type of {literalArgument} must equal the type of the argument definition OR - * The type of {literalArgument} must be coercible to type of the argument definition + * For each {argument} in the document + * Let {value} be the Value of {argument} + * If {value} is not a Variable + * Let {argumentName} be the Name of {argument}. + * Let {argumentDefinition} be the argument definition provided by the parent field or definition named {argumentName}. + * Let {type} be the type expected by {argumentDefinition}. + * The type of {literalArgument} must be coercible to {type}. ** Explanatory Text ** -Argument literal values must be compatible with the type defined on the type that -literal is being passed to. - -This means either - * the types must match equally or - * the types must be coercible. +Literal values must be compatible with the type defined by the argument they are +being provided to, as per the coercion rules defined in the Type System chapter. For example, an Int can be coerced into a Float. @@ -397,20 +395,22 @@ fragment stringIntoInt on Arguments { } ``` -#### Argument Optionality +#### Required Arguments - * For each {selection} in the document - * Let {arguments} be the set of argument provided to the {selection} - * Let {targetField} be the target field of a given {selection} - * Let {argumentDefinitions} be the set of argument definitions of {targetField} - * For each {definition} in {argumentDefinition}, if the type of {definition} is non-null - a value must be provided. + * 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 {definition} in {argumentDefinitions} + * Let {type} be the expected type of {definition} + * If {type} is Non-Null + * Let {argumentName} be the name of {definition} + * Let {argument} be the argument in {arguments} named {argumentName} + * {argument} must exist. ** Explanatory Text ** -Field arguments can be required. Field arguments are required if the type of the argument -is non-null. If it is not non-null, the argument is optional. Optional arguments -must have default values. +Arguments can be required. Arguments are required if the type of the argument +is non-null. If it is not non-null, the argument is optional. For example the following are valid: @@ -430,7 +430,6 @@ On a field with a a nullable arg, that argument can be omitted. Therefore the following query is valid: ```graphql - fragment goodBooleanArgDefault on Arguments { booleanArgField } @@ -776,43 +775,16 @@ and {Sentient}. ** Formal Specification ** - * For every {directiveUse} in a document. - * The name of that directive must be defined by the type system of - the GraphQL server. + * For every {directive} in a document. + * Let {directiveName} be the name of {directive}. + * Let {directiveDefinition} be the directive named {directiveName}. + * {directiveDefinition} must exist. ** Explanatory Text ** GraphQL servers define what directives they support. For each usage of a directive, the directive must be available on that server. -### Directive Arguments Are Of Correct Type - -** Formal Specification ** - - * For every {directiveUse} in a document. - * Let {directiveType} be the input type of the corresponding - directive defined on the server. - * If {directiveType} is not defined: - * The directive is meant to be used only as flag, and no argument should be - provided. - * If {directiveType} is defined and non-null: - * {directiveUse} must have an argument - * Let {argumentType} be the type of argument supplied to {directiveUse} - * {argumentType} and {directiveType} must be the same or {argumentType} must - be coercible to {directiveType} - -** Explanatory Text ** - -Directive arguments follow similar rules to arguments on fields. Much like -field arguments, arguments to directives must be of the same type or -coercible to input type of the directive type. - -Directives arguments differ from field arguments insofar as they can -be used without a provided argument. If the type of directive is not non-null, -the directive can be optionally used without an argument. If the type of -a directive is not defined, it is a flag directive: it cannot have an argument, -If a value is provided to a flag directive, this is a validation error. - ## Operations ### Variables diff --git a/Section 6 -- Execution.md b/Section 6 -- Execution.md index 25bf9bff2..2402d9b50 100644 --- a/Section 6 -- Execution.md +++ b/Section 6 -- Execution.md @@ -44,6 +44,12 @@ CollectFields(objectType, selectionSet, visitedFragments): * Initialize {groupedFields} to an empty list of lists. * For each {selection} in {selectionSet}; + * If {selection} provides the directive `@skip`, let {skipDirective} be that directive. + * If {skipDirective}'s {if} argument is {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 {false}, continue with the + next {selection} in {selectionSet}. * If {selection} is a Field: * Let {responseKey} be the response key of {selection}. * Let {groupForResponseKey} be the list in {groupedFields} for diff --git a/Section 8 -- Grammar.md b/Section 8 -- Grammar.md index faf9ab94e..cd9cdc931 100644 --- a/Section 8 -- Grammar.md +++ b/Section 8 -- Grammar.md @@ -314,9 +314,7 @@ behavior in a GraphQL document. Directives : Directive+ -Directive : - - @ Name - - @ Name : Value +Directive : @ Name Arguments? ### Types