From 3d43179e070d56e534da0f019ef42ef6202805e9 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 12 Jun 2020 16:21:52 +0100 Subject: [PATCH 01/43] First pass --- spec/Appendix B -- Grammar Summary.md | 13 +++++ spec/Section 2 -- Language.md | 2 +- spec/Section 3 -- Type System.md | 53 +++++++++++------ spec/Section 4 -- Introspection.md | 31 ++++++++-- spec/Section 5 -- Validation.md | 84 +++++++++++++++++++++++---- spec/Section 6 -- Execution.md | 32 +++++++--- 6 files changed, 174 insertions(+), 41 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index a0ab1494f..036dc7041 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -233,6 +233,7 @@ TypeDefinition : - ObjectTypeDefinition - InterfaceTypeDefinition - UnionTypeDefinition + - TaggedTypeDefinition - EnumTypeDefinition - InputObjectTypeDefinition @@ -241,6 +242,7 @@ TypeExtension : - ObjectTypeExtension - InterfaceTypeExtension - UnionTypeExtension + - TaggedTypeExtension - EnumTypeExtension - InputObjectTypeExtension @@ -264,6 +266,8 @@ FieldsDefinition : { FieldDefinition+ } FieldDefinition : Description? Name ArgumentsDefinition? : Type Directives[Const]? +TaggedFieldDefinition : Description? Name : Type Directives[Const]? + ArgumentsDefinition : ( InputValueDefinition+ ) InputValueDefinition : Description? Name : Type DefaultValue? Directives[Const]? @@ -285,6 +289,14 @@ UnionTypeExtension : - extend union Name Directives[Const]? UnionMemberTypes - extend union Name Directives[Const] +TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedFieldsDefinition? + +TaggedFieldsDefinition : { TaggedFieldDefinition+ } + +TaggedTypeExtension : + - extend tagged Name Directives[Const]? TaggedFieldsDefinition + - extend tagged Name Directives[Const] + EnumTypeDefinition : Description? enum Name Directives[Const]? EnumValuesDefinition? EnumValuesDefinition : { EnumValueDefinition+ } @@ -331,6 +343,7 @@ TypeSystemDirectiveLocation : one of `ARGUMENT_DEFINITION` `INTERFACE` `UNION` + `TAGGED` `ENUM` `ENUM_VALUE` `INPUT_OBJECT` diff --git a/spec/Section 2 -- Language.md b/spec/Section 2 -- Language.md index a4667adca..a791d3082 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -607,7 +607,7 @@ can be used in the context of querying a `User`. Fragments cannot be specified on any input value (scalar, enumeration, or input object). -Fragments can be specified on object types, interfaces, and unions. +Fragments can be specified on object types, tagged types, interfaces, and unions. Selections within fragments only return values when the concrete type of the object it is operating on matches the type of the fragment. diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 9a87d6a5b..9820e2e36 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -242,6 +242,7 @@ TypeDefinition : - UnionTypeDefinition - EnumTypeDefinition - InputObjectTypeDefinition + - TaggedTypeDefinition The fundamental unit of any GraphQL Schema is the type. There are six kinds of named type definitions in GraphQL, and two wrapping types. @@ -255,7 +256,7 @@ Scalars and Enums form the leaves in response trees; the intermediate levels are `Object` types, which define a set of fields, where each field is another type in the system, allowing the definition of arbitrary type hierarchies. -GraphQL supports two abstract types: interfaces and unions. +GraphQL supports three abstract types: interfaces, unions and tagged types. An `Interface` defines a list of fields; `Object` types and other Interface types which implement this Interface are guaranteed to implement those fields. @@ -266,6 +267,10 @@ A `Union` defines a list of possible types; similar to interfaces, whenever the type system claims a union will be returned, one of the possible types will be returned. +A `Tagged Type` defines a list of possible fields; whenever the type system +references a tagged type, exactly one of the possible fields must be present +and all others must be omitted. + Finally, oftentimes it is useful to provide complex structs as inputs to GraphQL field arguments or variables; the `Input Object` type allows the schema to define exactly what data is expected. @@ -297,7 +302,9 @@ like Scalar and Enum types, can be used as both input types and output types; other kinds of types can only be used in one or the other. Input Object types 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. +types depending on how the wrapped type may be used. Tagged types may be used +as input types or output types depending on how the types of their fields may +be used. IsInputType(type) : * If {type} is a List type or Non-Null type: @@ -305,6 +312,10 @@ IsInputType(type) : * Return IsInputType({unwrappedType}) * If {type} is a Scalar, Enum, or Input Object type: * Return {true} + * If {type} is a Tagged type: + * If there exists a field in {type} where {IsInputType(fieldType)} is {false} + * Return {false} + * Return {true} * Return {false} IsOutputType(type) : @@ -313,6 +324,10 @@ IsOutputType(type) : * Return IsOutputType({unwrappedType}) * If {type} is a Scalar, Object, Interface, Union, or Enum type: * Return {true} + * If {type} is a Tagged type: + * If there exists a field in {type} where {IsOutputType(fieldType)} is {false} + * Return {false} + * Return {true} * Return {false} @@ -325,6 +340,7 @@ TypeExtension : - UnionTypeExtension - EnumTypeExtension - InputObjectTypeExtension + - TaggedTypeExtension Type extensions are used to represent a GraphQL type which has been extended from some original type. For example, this might be used by a local service to @@ -645,9 +661,9 @@ Must only yield exactly that subset: } ``` -A field of an Object type may be a Scalar, Enum, another Object type, -an Interface, or a Union. Additionally, it may be any wrapping type whose -underlying base type is one of those five. +A field of an Object type may be a Scalar, Enum, another Object type, an +Interface, a Union or a Tagged type. Additionally, it may be any wrapping type +whose underlying base type is one of those five. For example, the `Person` type might include a `relationship`: @@ -914,8 +930,8 @@ May yield the result: } ``` -The type of an object field argument must be an input type (any type except an -Object, Interface, or Union type). +The type of an object field argument must be an input type (any type for which +{IsInputType(type)} returns true). ### Field Deprecation @@ -988,9 +1004,10 @@ GraphQL interfaces represent a list of named fields and their arguments. GraphQL objects and interfaces can then implement these interfaces which requires that the implementing type will define all fields defined by those interfaces. -Fields on a GraphQL interface have the same rules as fields on a GraphQL object; -their type can be Scalar, Object, Enum, Interface, or Union, or any wrapping -type whose base type is one of those five. +Fields on a GraphQL interface have the same rules as fields on a GraphQL +object; their type can be Scalar, Object, Enum, Interface, Union, Tagged types +where {IsOutputType(type)} returns {true}, or any wrapping type whose base type +is one of those six. For example, an interface `NamedEntity` may describe a required field and types such as `Person` or `Business` may then implement this interface to guarantee @@ -1319,8 +1336,8 @@ Union types have the potential to be invalid if incorrectly defined. 1. A Union type must include one or more unique member types. 2. The member types of a Union type must all be Object base types; - Scalar, Interface and Union types must not be member types of a Union. - Similarly, wrapping types must not be member types of a Union. + Scalar, Interface, Union and Tagged types must not be member types of a + Union. Similarly, wrapping types must not be member types of a Union. ### Union Extensions @@ -1340,8 +1357,8 @@ Union type extensions have the potential to be invalid if incorrectly defined. 1. The named type must already be defined and must be a Union type. 2. The member types of a Union type extension must all be Object base types; - Scalar, Interface and Union types must not be member types of a Union. - Similarly, wrapping types must not be member types of a Union. + Scalar, Interface, Union and Tagged types must not be member types of a + Union. Similarly, wrapping types must not be member types of a Union. 3. All member types of a Union type extension must be unique. 4. All member types of a Union type extension must not already be a member of the original Union type. @@ -1428,9 +1445,10 @@ InputFieldsDefinition : { InputValueDefinition+ } Fields may accept arguments to configure their behavior. These inputs are often scalars or enums, but they sometimes need to represent more complex values. -A GraphQL Input Object defines a set of input fields; the input fields are either -scalars, enums, or other input objects. This allows arguments to accept -arbitrarily complex structs. +A GraphQL Input Object defines a set of input fields; the input fields are +either scalars, enums, other input objects, or Tagged types for which +{IsInputType(type)} returns {true}. This allows arguments to accept arbitrarily +complex structs. In this example, an Input Object called `Point2D` describes `x` and `y` inputs: @@ -1747,6 +1765,7 @@ TypeSystemDirectiveLocation : one of `ENUM_VALUE` `INPUT_OBJECT` `INPUT_FIELD_DEFINITION` + `TAGGED` A GraphQL schema describes directives which are used to annotate various parts of a GraphQL document as an indicator that they should be evaluated differently diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index d1920cc63..45c8cac9b 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -91,7 +91,8 @@ warnings. GraphQL supports type name introspection at any point within a query by the meta-field `__typename: String!` when querying against any Object, Interface, -or Union. It returns the name of the object type currently being queried. +Union or Tagged. It returns the name of the object or tagged type currently +being queried. This is most often used when querying against Interface or Union types to identify which actual type of the possible types has been returned. @@ -130,7 +131,7 @@ type __Type { name: String description: String - # should be non-null for OBJECT and INTERFACE only, must be null for the others + # should be non-null for OBJECT, INTERFACE and TAGGED only, must be null for the others fields(includeDeprecated: Boolean = false): [__Field!] # should be non-null for OBJECT and INTERFACE only, must be null for the others @@ -179,6 +180,7 @@ enum __TypeKind { UNION ENUM INPUT_OBJECT + TAGGED LIST NON_NULL } @@ -206,6 +208,7 @@ enum __DirectiveLocation { ARGUMENT_DEFINITION INTERFACE UNION + TAGGED ENUM ENUM_VALUE INPUT_OBJECT @@ -216,8 +219,9 @@ enum __DirectiveLocation { ### The __Type Type -`__Type` is at the core of the type introspection system. -It represents scalars, interfaces, object types, unions, enums in the system. +`__Type` is at the core of the type introspection system. It represents +scalars, interfaces, object types, unions, enums and tagged types in the +system. `__Type` also represents type modifiers, which are used to modify a type that it refers to (`ofType: __Type`). This is how we represent lists, @@ -299,6 +303,25 @@ Fields * All other fields must return {null}. +#### Tagged + +Tagged types are an abstract type where exactly one field out of a list of +potential fields must be present. The possible fields of a tagged type are +explicitly listed out in `fields`. No modification of a type is necessary to +use it as the field type of a tagged type. + +Fields + +* `kind` must return `__TypeKind.TAGGED`. +* `name` must return a String. +* `description` may return a String or {null}. +* `fields`: The set of fields query-able on this type. + * Accepts the argument `includeDeprecated` which defaults to {false}. If + {true}, deprecated fields are also returned. + * All fields present must have zero arguments. +* All other fields must return {null}. + + #### Enum Enums are special scalars that can only have a defined set of values. diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 317e88de6..feb25235b 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -81,6 +81,19 @@ type Cat implements Pet { union CatOrDog = Cat | Dog union DogOrHuman = Dog | Human union HumanOrAlien = Human | Alien + +tagged Being { + cat: Cat + dog: Dog + human: Human + alien: Alien +} + +tagged Terran { + cat: Cat + dog: Dog + human: Human +} ``` @@ -324,7 +337,7 @@ must provide the operation name as described in {GetOperation()}. ## Fields -### Field Selections on Objects, Interfaces, and Unions Types +### Field Selections on Objects, Interfaces, Unions and Tagged Types **Formal Specification** @@ -568,7 +581,7 @@ fragment conflictingDifferingResponses on Pet { * Let {selectionType} be the result type of {selection} * If {selectionType} is a scalar or enum: * The subselection set of that selection must be empty -* If {selectionType} is an interface, union, or object +* If {selectionType} is an interface, union, object or tagged type * The subselection set of that selection must NOT BE empty **Explanatory Text** @@ -594,9 +607,9 @@ fragment scalarSelectionsNotAllowedOnInt on Dog { } ``` -Conversely the leaf field selections of GraphQL queries -must be of type scalar or enum. Leaf selections on objects, interfaces, -and unions without subfields are disallowed. +Conversely the leaf field selections of GraphQL queries must be of type scalar +or enum. Leaf selections on objects, interfaces, unions and tagged types +without subfields are disallowed. Let's assume the following additions to the query root type of the schema: @@ -605,6 +618,7 @@ extend type Query { human: Human pet: Pet catOrDog: CatOrDog + being: Being } ``` @@ -622,6 +636,10 @@ query directQueryOnInterfaceWithoutSubFields { query directQueryOnUnionWithoutSubFields { catOrDog } + +query directQueryOnTaggedWithoutSubFields { + being +} ``` @@ -895,14 +913,14 @@ fragment inlineNotExistingType on Dog { **Formal Specification** * For each {fragment} defined in the document. -* The target type of fragment must have kind {UNION}, {INTERFACE}, or - {OBJECT}. +* The target type of fragment must have kind {UNION}, {INTERFACE}, + {OBJECT} or {TAGGED}. **Explanatory Text** -Fragments can only be declared on unions, interfaces, and objects. They are -invalid on scalars. They can only be applied on non-leaf fields. This rule -applies to both inline and named fragments. +Fragments can only be declared on unions, interfaces, objects and tagged types. +They are invalid on scalars. They can only be applied on non-leaf fields. This +rule applies to both inline and named fragments. The following fragment declarations are valid: @@ -920,6 +938,12 @@ fragment fragOnUnion on CatOrDog { name } } + +fragment fragOnTagged on Being { + dog { + name + } +} ``` and the following are invalid: @@ -1095,6 +1119,7 @@ fragment ownerFragment on Human { GetPossibleTypes(type): * If {type} is an object type, return a set containing {type} + * If {type} is a tagged type, return a set containing {type} * If {type} is an interface type, return the set of types implementing {type} * If {type} is a union type, return the set of possible types of {type} @@ -1134,6 +1159,43 @@ fragment catInDogFragmentInvalid on Dog { ``` +##### Tagged Spreads In Tagged Scope + +In the scope of a tagged type, the only valid tagged type +fragment spread is one that applies to the same type that +is in scope. + +For example + +```graphql example +fragment beingFragment on Being { + ... on Being { + dog { + barkVolume + } + } +} +``` + +and the following is invalid + +```graphql counter-example +fragment beingFragment on Being { + ... on Terran { + dog { + barkVolume + } + } +} +``` + +This counter-example may be surprising since Being is covariant to Terran +(Being contains all the fields Terran contains), however allowing this spread +to be valid would inhibit schema evolution - we'd have to ensure that Being +always remained covariant to Terran, preventing us from adding fields to Terran +alone without adding them to Being. + + ##### Abstract Spreads in Object Scope In scope of an object type, unions or interface spreads can be used @@ -1572,7 +1634,7 @@ fragment HouseTrainedFragment on Query { **Explanatory Text** Variables can only be input types. Objects, unions, and interfaces cannot be -used as inputs. +used as inputs, neither can non-input tagged types. For these examples, consider the following typesystem additions: diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index f0f21f97c..bde4c0d79 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -320,10 +320,21 @@ First, the selection set is turned into a grouped field set; then, each represented field in the grouped field set produces an entry into a response map. +GetSingleTaggedFieldName(objectType, objectValue): + + * If {objectType} is not a Tagged type, return {null}. + * Let {keys} be the keys of {objectValue} that are field names of {objectType}. + * If the length of {keys} is not 1: + * Throw a field error. + * Let {key} be the first entry in {keys} + * Return {key}. + + ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues): + * Let {singleTaggedFieldName} be {GetSingleTaggedFieldName}(objectType, objectValue)}. * Let {groupedFieldSet} be the result of - {CollectFields(objectType, selectionSet, variableValues)}. + {CollectFields(objectType, selectionSet, variableValues, singleTaggedFieldName)}. * Initialize {resultMap} to an empty ordered map. * For each {groupedFieldSet} as {responseKey} and {fields}: * Let {fieldName} be the name of the first entry in {fields}. @@ -477,8 +488,9 @@ 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, singleTaggedFieldName, visitedFragments): + * If {singleTaggedFieldName} is not provided, initialize it to {null}. * If {visitedFragments} if not provided, initialize it to the empty set. * Initialize {groupedFields} to an empty ordered map of lists. * For each {selection} in {selectionSet}: @@ -489,6 +501,8 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): * If {includeDirective}'s {if} argument is not {true} and is not a variable in {variableValues} with the value {true}, continue with the next {selection} in {selectionSet}. * If {selection} is a {Field}: + * Let {fieldName} be the field name. + * If {singleTaggedFieldName} is not null and {singleTaggedFieldName} is not {fieldName}, continue with the next {selection} in {selectionSet}. * 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. @@ -507,7 +521,7 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): with the next {selection} in {selectionSet}. * Let {fragmentSelectionSet} be the top-level selection set of {fragment}. * Let {fragmentGroupedFieldSet} be the result of calling - {CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)}. + {CollectFields(objectType, fragmentSelectionSet, variableValues, singleTaggedFieldName, visitedFragments)}. * For each {fragmentGroup} in {fragmentGroupedFieldSet}: * Let {responseKey} be the response key shared by all fields in {fragmentGroup}. * Let {groupForResponseKey} be the list in {groupedFields} for @@ -518,7 +532,7 @@ CollectFields(objectType, selectionSet, variableValues, visitedFragments): * If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType, 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 {CollectFields(objectType, fragmentSelectionSet, variableValues, visitedFragments)}. + * Let {fragmentGroupedFieldSet} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, singleTaggedFieldName, visitedFragments)}. * For each {fragmentGroup} in {fragmentGroupedFieldSet}: * Let {responseKey} be the response key shared by all fields in {fragmentGroup}. * Let {groupForResponseKey} be the list in {groupedFields} for @@ -530,6 +544,8 @@ DoesFragmentTypeApply(objectType, fragmentType): * If {fragmentType} is an Object Type: * if {objectType} and {fragmentType} are the same type, return {true}, otherwise return {false}. + * If {fragmentType} is a Tagged Type: + * 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} otherwise return {false}. * If {fragmentType} is a Union: @@ -636,8 +652,8 @@ execution flow. ### Value Completion After resolving the value for a field, it is completed by ensuring it adheres -to the expected return type. If the return type is another Object type, then -the field execution process continues recursively. +to the expected return type. If the return type is another Object or Tagged +type, then the field execution process continues recursively. CompleteValue(fieldType, fields, result, variableValues): * If the {fieldType} is a Non-Null type: @@ -657,8 +673,8 @@ CompleteValue(fieldType, fields, result, variableValues): * If {fieldType} is a Scalar or Enum type: * Return the result of "coercing" {result}, ensuring it is a legal value of {fieldType}, otherwise {null}. - * If {fieldType} is an Object, Interface, or Union type: - * If {fieldType} is an Object type. + * If {fieldType} is an Object, Interface, Union or Tagged type: + * If {fieldType} is an Object type or Tagged type. * Let {objectType} be {fieldType}. * Otherwise if {fieldType} is an Interface or Union type. * Let {objectType} be {ResolveAbstractType(fieldType, result)}. From d411b60e2df85f6599a8ded63028ba3938cc1db7 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 12 Jun 2020 17:22:05 +0100 Subject: [PATCH 02/43] More edits --- spec/Section 3 -- Type System.md | 200 ++++++++++++++++++++++++++++++- spec/Section 6 -- Execution.md | 4 +- 2 files changed, 200 insertions(+), 4 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 9820e2e36..df9db3a62 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -663,7 +663,7 @@ Must only yield exactly that subset: A field of an Object type may be a Scalar, Enum, another Object type, an Interface, a Union or a Tagged type. Additionally, it may be any wrapping type -whose underlying base type is one of those five. +whose underlying base type is one of those six. For example, the `Person` type might include a `relationship`: @@ -810,8 +810,8 @@ Produces the ordered result: **Result Coercion** -Determining the result of coercing an object is the heart of the GraphQL -executor, so this is covered in that section of the spec. +Determining the result of coercing an object or tagged type is the heart of the +GraphQL executor, so this is covered in that section of the spec. **Input Coercion** @@ -1365,6 +1365,200 @@ Union type extensions have the potential to be invalid if incorrectly defined. 5. Any non-repeatable directives provided must not already apply to the original Union type. + +## Tagged Types + +TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedFieldsDefinition? + +TaggedTypeExtension : + - extend tagged Name Directives[Const]? TaggedFieldsDefinition + - extend tagged Name Directives[Const] + +TaggedFieldsDefinition : { TaggedFieldDefinition+ } + +TaggedFieldDefinition : Description? Name : Type Directives[Const]? + +Tagged types represent a list of possible named fields, exactly one of which +must be present. This resulting field must yield a value of a specific type. +In outputs, like for Objects, Tagged types should be serialized as ordered +maps: the queried field names (or aliases) that are present are the keys and +the result of evaluating the field is the value, ordered by the order in which +they appear in the query. Note that Tagged types may be objects with more than +one key even though they guarantee that exactly one field is present, this is +because aliases may be used, and `__typename` may also be queried. + +All fields defined within a Tagged type must not have a name which begins with +{"__"} (two underscores), as this is used exclusively by GraphQL's +introspection system. + +For example, a type `StringFilter` could be described as: + +```graphql example +tagged StringFilter { + contains: String! + lengthAtLeast: Int! + lengthAtMost: Int! +} +``` + +In this case we're representing exactly one of the following: + +- an object containing a single key `contains` with a {String} value +- an object containing a single key `lengthAtLeast` with an {Int} value +- an object containing a single key `lengthAtMost` with an {Int} value + +The `StringFilter` Tagged type is valid as both an input and output type, but +this is not true of all tagged types. If a Tagged type has a field whose type +is only valid for output, then the Tagged type is only valid for output. If a +Tagged type has a field whose type is only valid for input, then the Tagged +type is only valid for input. If a Tagged type has a field that is only valid +for input, and another field that's only valid for output, then it is not a +valid Tagged type. + +A field of a Tagged type may be a Scalar, Enum, Object type, an Interface, a +Union or another Tagged type. Additionally, it may be any wrapping type whose +underlying base type is one of those six. + +Selecting all the fields of our `StringFilter` type: + +```graphql example +{ + contains + lengthAtLeast + lengthAtMost +} +``` + +Could yield one of the following objects: + +- `{ "contains": "Awesome" }` +- `{ "lengthAtLeast": 3 }` +- `{ "lengthAtMost": 42 }` + +Valid queries must supply a nested field set for a field that returns a Tagged +type, so for this schema: + +```graphql example +type Query { + stringFilter: StringFilter +} +``` + +This query is not valid: + +```graphql counter-example +{ + stringFilter +} +``` + +However, this query is valid: + +```graphql example +{ + stringFilter { + contains + } +} +``` + +And may yield one of the following objects: + +- `{ "contains": "Awesome" }` +- `{}` + +**Field Ordering** + +Like with Object types, when querying a Tagged type, the resulting mapping of +fields are conceptually ordered in the same order in which they were +encountered during query execution, excluding fragments for which the type does +not apply and fields or fragments that are skipped via `@skip` or `@include` +directives, and fields which are neither the single matched field nor the +`__typename` introspection field. This ordering is correctly produced when +using the {CollectFields()} algorithm. + +**Result Coercion** + +Determining the result of coercing an object or tagged type is the heart of the +GraphQL executor, so this is covered in that section of the spec. + +**Input Coercion** + +TODO + +**Type Validation** + +Tagged types have the potential to be invalid if incorrectly defined. This set +of rules must be adhered to by every Tagged type in a GraphQL schema. + +1. A Tagged type must define one or more fields. +2. For each field of a Tagged type: + 1. The field must have a unique name within that Tagged type; + no two fields may share the same name. + 2. The field must not have a name which begins with the + characters {"__"} (two underscores). + 3. The field must not have any arguments. +3. {IsOutputType(fieldType)} and {IsInputType(type)} cannot both be {false}. + +### Field Deprecation + +Fields in a Tagged type may be marked as deprecated as deemed necessary by the +application. It is still legal to query for these fields (to ensure existing +clients are not broken by the change), but the fields should be appropriately +treated in documentation and tooling. + +When using the type system definition language, `@deprecated` directives are +used to indicate that a field is deprecated: + +```graphql example +tagged ExampleType { + oldField: String @deprecated +} +``` + +### Tagged Extensions + +TaggedTypeExtension : + - extend tagged Name Directives[Const]? TaggedFieldsDefinition + - extend tagged Name Directives[Const] + +Tagged type extensions are used to represent a Tagged type which has been +extended from some original Tagged type. For example, this might be used to +represent a GraphQL service which is itself an extension of another GraphQL +service. + +In this example, an additional option `startsWith` is added to a `StringFilter` +type: + +```graphql example +extend tagged StringFilter { + startsWith: String! +} +``` + +Tagged type extensions may choose not to add additional fields, instead only +adding directives. + +In this example, a directive is added to a `StringFilter` type without adding +fields: + +```graphql example +extend tagged StringFilter @addedDirective +``` + +**Type Validation** + +Tagged type extensions have the potential to be invalid if incorrectly defined. + +1. The named type must already be defined and must be a Tagged type. +2. The fields of a Tagged type extension must have unique names; no two fields + may share the same name. +3. Any fields of a Tagged type extension must not be already defined on the + original Tagged type. +4. Any non-repeatable directives provided must not already apply to the + original Tagged type. + + ## Enums EnumTypeDefinition : Description? enum Name Directives[Const]? EnumValuesDefinition? diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index bde4c0d79..e07d2a170 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -502,7 +502,9 @@ CollectFields(objectType, selectionSet, variableValues, singleTaggedFieldName, v {selection} in {selectionSet}. * If {selection} is a {Field}: * Let {fieldName} be the field name. - * If {singleTaggedFieldName} is not null and {singleTaggedFieldName} is not {fieldName}, continue with the next {selection} in {selectionSet}. + * If {singleTaggedFieldName} is not null: + * If {fieldName} is not {singleTaggedFieldName} and {fieldName} is not an introspection field (beginning with the characters {"__"} (two underscores)): + * Continue with the next {selection} in {selectionSet}. * 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. From 16de65758742a7e4cdd9d5b0c7af1743cc6dcc68 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 12 Jun 2020 17:48:50 +0100 Subject: [PATCH 03/43] Input coercion --- spec/Section 3 -- Type System.md | 69 +++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index df9db3a62..b75b26a22 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1480,11 +1480,76 @@ using the {CollectFields()} algorithm. **Result Coercion** Determining the result of coercing an object or tagged type is the heart of the -GraphQL executor, so this is covered in that section of the spec. +GraphQL executor, so this is covered in that section of the spec. Note that +only Tagged types for which {IsOutputType(type)} returns {true} may be used in +output. **Input Coercion** -TODO +Only Tagged types for which {IsInputType(type)} returns {true} may be used as +input. + +The value for an input Tagged type should be an input object literal or an +unordered map supplied by a variable, otherwise a query error must be thrown. +In either case, the input object literal or unordered map must not contain any +entries with names not defined by a field of this Tagged type, otherwise an +error must be thrown. Similarly the input object literal or unordered map must +contain exactly one field, otherwise an error must be thrown. + +The result of coercion is an unordered map with an entry for exactly one field +both defined by the input object type and for which a value exists. The +resulting map is constructed with the following rules: + +* If more than one field is defined in the input, an error should be thrown. + +* For the single field defined in the input, if the value is {null} and the + field's type is a non-null type, an error should be throw. + +* If a literal value is provided for an input object field, an entry in the + coerced unordered map is given the result of coercing that value according + to the input coercion rules for the type of that field. + +* If a variable is provided for an input object field, the runtime value of that + variable must be used. If the runtime value is {null} and the field type + is non-null, a field error must be thrown. If no runtime value is provided, + the variable definition's default value should be used. If the variable + definition does not provide a default value, the field should be treated as + if it was not specified. + +* No entry should be added to the unordered map for any other fields defined on + the Tagged type. + +Following are examples of input coercion for a Tagged type with a +`String` field `a` and a required (non-null) `Int!` field `b`: + +```graphql example +tagged ExampleInputTagged { + a: String + b: Int! +} +``` + +Literal Value | Variables | Coerced Value +------------------------ | ----------------------- | --------------------------- +`{ a: "abc", b: 123 }` | `{}` | Error: Exactly one key must be specified +`{ a: null, b: 123 }` | `{}` | Error: Exactly one key must be specified +`{ b: 123 }` | `{}` | `{ b: 123 }` +`{ a: $var, b: 123 }` | `{ var: null }` | Error: Exactly one key must be specified +`{ a: $var, b: 123 }` | `{}` | `{ b: 123 }` +`{ b: $var }` | `{ var: 123 }` | `{ b: 123 }` +`$var` | `{ var: { b: 123 } }` | `{ b: 123 }` +`"abc123"` | `{}` | Error: Incorrect value +`$var` | `{ var: "abc123" } }` | Error: Incorrect value +`{ a: "abc", b: "123" }` | `{}` | Error: Exactly one key must be specified +`{ b: "123" }` | `{}` | Error: Incorrect value for field {b} +`{ a: "abc" }` | `{}` | `{ a: "abc" }` +`{ b: $var }` | `{}` | Error: No keys were specified +`$var` | `{ var: { a: "abc" } }` | `{ a: "abc" }` +`{ a: "abc", b: null }` | `{}` | Error: Exactly one key must be specified +`{ b: $var }` | `{ var: null }` | Error: {b} must be non-null. +`{ b: 123, c: "xyz" }` | `{}` | Error: Unexpected field {c} + + **Type Validation** From a73d1cc37a55f7cee6c762c99e2c9df201e4e6f0 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Fri, 3 Jul 2020 12:35:38 +0100 Subject: [PATCH 04/43] Change tagged "fields" to "members" --- spec/Appendix B -- Grammar Summary.md | 8 +- spec/Section 3 -- Type System.md | 123 ++++++++++++++------------ spec/Section 4 -- Introspection.md | 40 +++++++-- spec/Section 5 -- Validation.md | 9 +- spec/Section 6 -- Execution.md | 23 ++--- 5 files changed, 121 insertions(+), 82 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index 036dc7041..dd2dd6a1b 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -266,7 +266,7 @@ FieldsDefinition : { FieldDefinition+ } FieldDefinition : Description? Name ArgumentsDefinition? : Type Directives[Const]? -TaggedFieldDefinition : Description? Name : Type Directives[Const]? +TaggedMemberDefinition : Description? Name : Type Directives[Const]? ArgumentsDefinition : ( InputValueDefinition+ ) @@ -289,12 +289,12 @@ UnionTypeExtension : - extend union Name Directives[Const]? UnionMemberTypes - extend union Name Directives[Const] -TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedFieldsDefinition? +TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedMembersDefinition? -TaggedFieldsDefinition : { TaggedFieldDefinition+ } +TaggedMembersDefinition : { TaggedMemberDefinition+ } TaggedTypeExtension : - - extend tagged Name Directives[Const]? TaggedFieldsDefinition + - extend tagged Name Directives[Const]? TaggedMembersDefinition - extend tagged Name Directives[Const] EnumTypeDefinition : Description? enum Name Directives[Const]? EnumValuesDefinition? diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index b75b26a22..6602bdb3f 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -256,7 +256,7 @@ Scalars and Enums form the leaves in response trees; the intermediate levels are `Object` types, which define a set of fields, where each field is another type in the system, allowing the definition of arbitrary type hierarchies. -GraphQL supports three abstract types: interfaces, unions and tagged types. +GraphQL supports two abstract types: interfaces, and unions. An `Interface` defines a list of fields; `Object` types and other Interface types which implement this Interface are guaranteed to implement those fields. @@ -267,8 +267,8 @@ A `Union` defines a list of possible types; similar to interfaces, whenever the type system claims a union will be returned, one of the possible types will be returned. -A `Tagged Type` defines a list of possible fields; whenever the type system -references a tagged type, exactly one of the possible fields must be present +A `Tagged Type` defines a list of possible members; whenever the type system +references a tagged type, exactly one of the possible members must be present and all others must be omitted. Finally, oftentimes it is useful to provide complex structs as inputs to @@ -303,7 +303,7 @@ other kinds of types can only be used in one or the other. Input Object types ca 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. Tagged types may be used -as input types or output types depending on how the types of their fields may +as input types or output types depending on how the types of their members may be used. IsInputType(type) : @@ -313,7 +313,7 @@ IsInputType(type) : * If {type} is a Scalar, Enum, or Input Object type: * Return {true} * If {type} is a Tagged type: - * If there exists a field in {type} where {IsInputType(fieldType)} is {false} + * If there exists a member in {type} where {IsInputType(memberType)} is {false} * Return {false} * Return {true} * Return {false} @@ -325,7 +325,7 @@ IsOutputType(type) : * If {type} is a Scalar, Object, Interface, Union, or Enum type: * Return {true} * If {type} is a Tagged type: - * If there exists a field in {type} where {IsOutputType(fieldType)} is {false} + * If there exists a member in {type} where {IsOutputType(memberType)} is {false} * Return {false} * Return {true} * Return {false} @@ -1368,26 +1368,27 @@ Union type extensions have the potential to be invalid if incorrectly defined. ## Tagged Types -TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedFieldsDefinition? +TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedMembersDefinition? TaggedTypeExtension : - - extend tagged Name Directives[Const]? TaggedFieldsDefinition + - extend tagged Name Directives[Const]? TaggedMembersDefinition - extend tagged Name Directives[Const] -TaggedFieldsDefinition : { TaggedFieldDefinition+ } +TaggedMembersDefinition : { TaggedMemberDefinition+ } -TaggedFieldDefinition : Description? Name : Type Directives[Const]? +TaggedMemberDefinition : Description? Name : Type Directives[Const]? -Tagged types represent a list of possible named fields, exactly one of which -must be present. This resulting field must yield a value of a specific type. +Tagged types represent a list of possible named members, exactly one of which +must be present. This resulting member must yield a value of a specific type. In outputs, like for Objects, Tagged types should be serialized as ordered -maps: the queried field names (or aliases) that are present are the keys and -the result of evaluating the field is the value, ordered by the order in which +maps: the queried member names (or aliases) that are present are the keys and +the result of evaluating the member is the value, ordered by the order in which they appear in the query. Note that Tagged types may be objects with more than -one key even though they guarantee that exactly one field is present, this is -because aliases may be used, and `__typename` may also be queried. +one key even though they guarantee that exactly one member is present, this is +because aliases may be used, and `__typename` may also be queried (and will +return the name of the tagged type). -All fields defined within a Tagged type must not have a name which begins with +All members defined within a Tagged type must not have a name which begins with {"__"} (two underscores), as this is used exclusively by GraphQL's introspection system. @@ -1408,18 +1409,19 @@ In this case we're representing exactly one of the following: - an object containing a single key `lengthAtMost` with an {Int} value The `StringFilter` Tagged type is valid as both an input and output type, but -this is not true of all tagged types. If a Tagged type has a field whose type +this is not true of all tagged types. If a Tagged type has a member whose type is only valid for output, then the Tagged type is only valid for output. If a -Tagged type has a field whose type is only valid for input, then the Tagged -type is only valid for input. If a Tagged type has a field that is only valid -for input, and another field that's only valid for output, then it is not a +Tagged type has a member whose type is only valid for input, then the Tagged +type is only valid for input. If a Tagged type has a member that is only valid +for input, and another member that's only valid for output, then it is not a valid Tagged type. -A field of a Tagged type may be a Scalar, Enum, Object type, an Interface, a -Union or another Tagged type. Additionally, it may be any wrapping type whose +A member of a Tagged type may be a Scalar, Enum, Input Object type, Object +type, an Interface, a Union or another Tagged type. Additionally, it may be +any wrapping type (e.g. list, non-null, or any combination thereof) whose underlying base type is one of those six. -Selecting all the fields of our `StringFilter` type: +Selecting all the members of our `StringFilter` type: ```graphql example { @@ -1473,7 +1475,7 @@ Like with Object types, when querying a Tagged type, the resulting mapping of fields are conceptually ordered in the same order in which they were encountered during query execution, excluding fragments for which the type does not apply and fields or fragments that are skipped via `@skip` or `@include` -directives, and fields which are neither the single matched field nor the +directives, and fields which are neither the single matched member nor the `__typename` introspection field. This ordering is correctly produced when using the {CollectFields()} algorithm. @@ -1492,13 +1494,13 @@ input. The value for an input Tagged type should be an input object literal or an unordered map supplied by a variable, otherwise a query error must be thrown. In either case, the input object literal or unordered map must not contain any -entries with names not defined by a field of this Tagged type, otherwise an +entries with names not defined by a member of this Tagged type, otherwise an error must be thrown. Similarly the input object literal or unordered map must -contain exactly one field, otherwise an error must be thrown. +contain exactly one member, otherwise an error must be thrown. -The result of coercion is an unordered map with an entry for exactly one field -both defined by the input object type and for which a value exists. The -resulting map is constructed with the following rules: +The result of coercion is an unordered map with an entry for exactly one +member both defined by the tagged type and present in the input. The resulting +map is constructed with the following rules: * If more than one field is defined in the input, an error should be thrown. @@ -1507,20 +1509,20 @@ resulting map is constructed with the following rules: * If a literal value is provided for an input object field, an entry in the coerced unordered map is given the result of coercing that value according - to the input coercion rules for the type of that field. + to the input coercion rules for the type of the tagged member for that field. * If a variable is provided for an input object field, the runtime value of that - variable must be used. If the runtime value is {null} and the field type - is non-null, a field error must be thrown. If no runtime value is provided, - the variable definition's default value should be used. If the variable - definition does not provide a default value, the field should be treated as - if it was not specified. + variable must be used. If the runtime value is {null} and the tagged member + type is non-null, a field error must be thrown. If no runtime value is + provided, the variable definition's default value should be used. If the + variable definition does not provide a default value, the field should be + treated as if it was not specified. -* No entry should be added to the unordered map for any other fields defined on - the Tagged type. +* No entry should be added to the unordered map for any other members defined + on the Tagged type. Following are examples of input coercion for a Tagged type with a -`String` field `a` and a required (non-null) `Int!` field `b`: +`String` member `a` and a required (non-null) `Int!` member `b`: ```graphql example tagged ExampleInputTagged { @@ -1541,13 +1543,13 @@ Literal Value | Variables | Coerced Value `"abc123"` | `{}` | Error: Incorrect value `$var` | `{ var: "abc123" } }` | Error: Incorrect value `{ a: "abc", b: "123" }` | `{}` | Error: Exactly one key must be specified -`{ b: "123" }` | `{}` | Error: Incorrect value for field {b} +`{ b: "123" }` | `{}` | Error: Incorrect value for member {b} `{ a: "abc" }` | `{}` | `{ a: "abc" }` `{ b: $var }` | `{}` | Error: No keys were specified `$var` | `{ var: { a: "abc" } }` | `{ a: "abc" }` `{ a: "abc", b: null }` | `{}` | Error: Exactly one key must be specified `{ b: $var }` | `{ var: null }` | Error: {b} must be non-null. -`{ b: 123, c: "xyz" }` | `{}` | Error: Unexpected field {c} +`{ b: 123, c: "xyz" }` | `{}` | Error: Unexpected member {c} @@ -1556,21 +1558,21 @@ Literal Value | Variables | Coerced Value Tagged types have the potential to be invalid if incorrectly defined. This set of rules must be adhered to by every Tagged type in a GraphQL schema. -1. A Tagged type must define one or more fields. -2. For each field of a Tagged type: - 1. The field must have a unique name within that Tagged type; - no two fields may share the same name. - 2. The field must not have a name which begins with the +1. A Tagged type must define one or more members. +2. For each member of a Tagged type: + 1. The member must have a unique name within that Tagged type; + no two members may share the same name. + 2. The member must not have a name which begins with the characters {"__"} (two underscores). - 3. The field must not have any arguments. 3. {IsOutputType(fieldType)} and {IsInputType(type)} cannot both be {false}. -### Field Deprecation +### Member Deprecation -Fields in a Tagged type may be marked as deprecated as deemed necessary by the -application. It is still legal to query for these fields (to ensure existing -clients are not broken by the change), but the fields should be appropriately -treated in documentation and tooling. +Members in a Tagged type may be marked as deprecated as deemed necessary by +the application. It is still legal to query for these members on outputs or +supply these members in inputs (to ensure existing clients are not broken by +the change), but the members should be appropriately treated in documentation +and tooling. When using the type system definition language, `@deprecated` directives are used to indicate that a field is deprecated: @@ -1581,10 +1583,13 @@ tagged ExampleType { } ``` +Note: a deprecated member of a tagged type may legitimately be marked as +non-nullable since this member need not be supplied/returned. + ### Tagged Extensions TaggedTypeExtension : - - extend tagged Name Directives[Const]? TaggedFieldsDefinition + - extend tagged Name Directives[Const]? TaggedMembersDefinition - extend tagged Name Directives[Const] Tagged type extensions are used to represent a Tagged type which has been @@ -1601,11 +1606,11 @@ extend tagged StringFilter { } ``` -Tagged type extensions may choose not to add additional fields, instead only +Tagged type extensions may choose not to add additional members, instead only adding directives. In this example, a directive is added to a `StringFilter` type without adding -fields: +members: ```graphql example extend tagged StringFilter @addedDirective @@ -1616,11 +1621,13 @@ extend tagged StringFilter @addedDirective Tagged type extensions have the potential to be invalid if incorrectly defined. 1. The named type must already be defined and must be a Tagged type. -2. The fields of a Tagged type extension must have unique names; no two fields +2. The members of a Tagged type extension must have unique names; no two members may share the same name. -3. Any fields of a Tagged type extension must not be already defined on the +3. The members of a Tagged type extension must not have names beginning with + {"__"}. +4. Any members of a Tagged type extension must not be already defined on the original Tagged type. -4. Any non-repeatable directives provided must not already apply to the +5. Any non-repeatable directives provided must not already apply to the original Tagged type. diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 45c8cac9b..a7639f0ee 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -131,7 +131,7 @@ type __Type { name: String description: String - # should be non-null for OBJECT, INTERFACE and TAGGED only, must be null for the others + # should be non-null for OBJECT and INTERFACE only, must be null for the others fields(includeDeprecated: Boolean = false): [__Field!] # should be non-null for OBJECT and INTERFACE only, must be null for the others @@ -146,6 +146,9 @@ type __Type { # should be non-null for INPUT_OBJECT only, must be null for the others inputFields: [__InputValue!] + # should be non-null for TAGGED only, must be null for the others + members(includeDeprecated: Boolean = false): [__Member!] + # should be non-null for NON_NULL and LIST only, must be null for the others ofType: __Type } @@ -166,6 +169,14 @@ type __InputValue { defaultValue: String } +type __Member { + name: String! + description: String + type: __Type! + isDeprecated: Boolean! + deprecationReason: String +} + type __EnumValue { name: String! description: String @@ -305,20 +316,19 @@ Fields #### Tagged -Tagged types are an abstract type where exactly one field out of a list of -potential fields must be present. The possible fields of a tagged type are -explicitly listed out in `fields`. No modification of a type is necessary to -use it as the field type of a tagged type. +Tagged types are an type where exactly one member out of a list of potential +members must be present. The possible members of a tagged type are explicitly +listed out in `members`. No modification of a type is necessary to use it as +the member type of a tagged type. Fields * `kind` must return `__TypeKind.TAGGED`. * `name` must return a String. * `description` may return a String or {null}. -* `fields`: The set of fields query-able on this type. +* `members`: The set of members query-able on this type. * Accepts the argument `includeDeprecated` which defaults to {false}. If - {true}, deprecated fields are also returned. - * All fields present must have zero arguments. + {true}, deprecated members are also returned. * All other fields must return {null}. @@ -419,6 +429,20 @@ Fields default value used by this input value in the condition a value is not provided at runtime. If this input value has no default value, returns {null}. +### The __Member Type + +The `__Member` type represents `members` of a tagged type. + +Fields + +* `name` must return a String +* `description` may return a String or {null} +* `type` must return a `__Type` that represents the type this member expects. +* `isDeprecated` returns {true} if this member should no longer be used, + otherwise {false}. +* `deprecationReason` optionally provides a reason why this member is deprecated. + + ### The __EnumValue Type The `__EnumValue` type represents one of possible values of an enum. diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index feb25235b..cdb5f03f4 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -339,6 +339,7 @@ must provide the operation name as described in {GetOperation()}. ### Field Selections on Objects, Interfaces, Unions and Tagged Types + **Formal Specification** * For each {selection} in the document. @@ -410,6 +411,10 @@ fragment directFieldSelectionOnUnion on CatOrDog { } ``` +Tagged types define "members", but for the purposes of this section the members +fill the same role as fields of an object type or interface. + +Note: how best to phrase this/factor it in? ### Field Selection Merging @@ -1190,9 +1195,9 @@ fragment beingFragment on Being { ``` This counter-example may be surprising since Being is covariant to Terran -(Being contains all the fields Terran contains), however allowing this spread +(Being contains all the members Terran contains), however allowing this spread to be valid would inhibit schema evolution - we'd have to ensure that Being -always remained covariant to Terran, preventing us from adding fields to Terran +always remained covariant to Terran, preventing us from adding members to Terran alone without adding them to Being. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index e07d2a170..55c4eda72 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -316,14 +316,17 @@ To execute a selection set, the object value being evaluated and the object type need to be known, as well as whether it must be executed serially, or may be executed in parallel. +For the purposes of execution, the term "field" may be used to reference a +"member" of a tagged type; this simplifies the wording of the algorithm. + First, the selection set is turned into a grouped field set; then, each represented field in the grouped field set produces an entry into a response map. -GetSingleTaggedFieldName(objectType, objectValue): +GetTaggedMemberName(objectType, objectValue): * If {objectType} is not a Tagged type, return {null}. - * Let {keys} be the keys of {objectValue} that are field names of {objectType}. + * Let {keys} be the keys of {objectValue} that are member names of {objectType}. * If the length of {keys} is not 1: * Throw a field error. * Let {key} be the first entry in {keys} @@ -332,9 +335,9 @@ GetSingleTaggedFieldName(objectType, objectValue): ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues): - * Let {singleTaggedFieldName} be {GetSingleTaggedFieldName}(objectType, objectValue)}. + * Let {taggedMemberName} be {GetTaggedMemberName}(objectType, objectValue)}. * Let {groupedFieldSet} be the result of - {CollectFields(objectType, selectionSet, variableValues, singleTaggedFieldName)}. + {CollectFields(objectType, selectionSet, variableValues, taggedMemberName)}. * Initialize {resultMap} to an empty ordered map. * For each {groupedFieldSet} as {responseKey} and {fields}: * Let {fieldName} be the name of the first entry in {fields}. @@ -488,9 +491,9 @@ 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, singleTaggedFieldName, visitedFragments): +CollectFields(objectType, selectionSet, variableValues, taggedMemberName, visitedFragments): - * If {singleTaggedFieldName} is not provided, initialize it to {null}. + * If {taggedMemberName} is not provided, initialize it to {null}. * If {visitedFragments} if not provided, initialize it to the empty set. * Initialize {groupedFields} to an empty ordered map of lists. * For each {selection} in {selectionSet}: @@ -502,8 +505,8 @@ CollectFields(objectType, selectionSet, variableValues, singleTaggedFieldName, v {selection} in {selectionSet}. * If {selection} is a {Field}: * Let {fieldName} be the field name. - * If {singleTaggedFieldName} is not null: - * If {fieldName} is not {singleTaggedFieldName} and {fieldName} is not an introspection field (beginning with the characters {"__"} (two underscores)): + * If {taggedMemberName} is not null: + * If {fieldName} is not {taggedMemberName} and {fieldName} is not an introspection field (beginning with the characters {"__"} (two underscores)): * Continue with the next {selection} in {selectionSet}. * Let {responseKey} be the response key of {selection} (the alias if defined, otherwise the field name). * Let {groupForResponseKey} be the list in {groupedFields} for @@ -523,7 +526,7 @@ CollectFields(objectType, selectionSet, variableValues, singleTaggedFieldName, v with the next {selection} in {selectionSet}. * Let {fragmentSelectionSet} be the top-level selection set of {fragment}. * Let {fragmentGroupedFieldSet} be the result of calling - {CollectFields(objectType, fragmentSelectionSet, variableValues, singleTaggedFieldName, visitedFragments)}. + {CollectFields(objectType, fragmentSelectionSet, variableValues, taggedMemberName, visitedFragments)}. * For each {fragmentGroup} in {fragmentGroupedFieldSet}: * Let {responseKey} be the response key shared by all fields in {fragmentGroup}. * Let {groupForResponseKey} be the list in {groupedFields} for @@ -534,7 +537,7 @@ CollectFields(objectType, selectionSet, variableValues, singleTaggedFieldName, v * If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType, 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 {CollectFields(objectType, fragmentSelectionSet, variableValues, singleTaggedFieldName, visitedFragments)}. + * Let {fragmentGroupedFieldSet} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, taggedMemberName, visitedFragments)}. * For each {fragmentGroup} in {fragmentGroupedFieldSet}: * Let {responseKey} be the response key shared by all fields in {fragmentGroup}. * Let {groupForResponseKey} be the list in {groupedFields} for From e65126dab0792f0593f379d852598407fb2f9f25 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 15 Jul 2020 17:05:19 +0100 Subject: [PATCH 05/43] Sync type system --- spec/Section 3 -- Type System.md | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 6602bdb3f..157c010b2 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -2027,6 +2027,7 @@ TypeSystemDirectiveLocation : one of `ARGUMENT_DEFINITION` `INTERFACE` `UNION` + `TAGGED` `ENUM` `ENUM_VALUE` `INPUT_OBJECT` From a4c457899f3b8ae7a86a0d447f50d9eb07fc7d6b Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 21 Jul 2020 17:36:31 +0100 Subject: [PATCH 06/43] Factor in review feedback from @spawnia --- spec/Section 3 -- Type System.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 157c010b2..a57211f1c 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -661,8 +661,8 @@ Must only yield exactly that subset: } ``` -A field of an Object type may be a Scalar, Enum, another Object type, an -Interface, a Union or a Tagged type. Additionally, it may be any wrapping type +A field of an Object type may be a Scalar, Enum, another Object type, Tagged +type, an Interface, or a Union. Additionally, it may be any wrapping type whose underlying base type is one of those six. For example, the `Person` type might include a `relationship`: @@ -1396,17 +1396,17 @@ For example, a type `StringFilter` could be described as: ```graphql example tagged StringFilter { + startsWith: String! contains: String! lengthAtLeast: Int! - lengthAtMost: Int! } ``` In this case we're representing exactly one of the following: +- an object containing a single key `startsWith` with an {String} value - an object containing a single key `contains` with a {String} value - an object containing a single key `lengthAtLeast` with an {Int} value -- an object containing a single key `lengthAtMost` with an {Int} value The `StringFilter` Tagged type is valid as both an input and output type, but this is not true of all tagged types. If a Tagged type has a member whose type @@ -1425,17 +1425,17 @@ Selecting all the members of our `StringFilter` type: ```graphql example { + startsWith contains lengthAtLeast - lengthAtMost } ``` Could yield one of the following objects: -- `{ "contains": "Awesome" }` +- `{ "startsWith": "GraphQL is" }` +- `{ "contains": "awesome" }` - `{ "lengthAtLeast": 3 }` -- `{ "lengthAtMost": 42 }` Valid queries must supply a nested field set for a field that returns a Tagged type, so for this schema: @@ -1466,7 +1466,7 @@ However, this query is valid: And may yield one of the following objects: -- `{ "contains": "Awesome" }` +- `{ "contains": "awesome" }` - `{}` **Field Ordering** @@ -1482,9 +1482,10 @@ using the {CollectFields()} algorithm. **Result Coercion** Determining the result of coercing an object or tagged type is the heart of the -GraphQL executor, so this is covered in that section of the spec. Note that -only Tagged types for which {IsOutputType(type)} returns {true} may be used in -output. +GraphQL executor, so this is covered in that section of the spec. + +Note only Tagged types for which {IsOutputType(type)} returns {true} may be +used in output. **Input Coercion** @@ -1495,17 +1496,17 @@ The value for an input Tagged type should be an input object literal or an unordered map supplied by a variable, otherwise a query error must be thrown. In either case, the input object literal or unordered map must not contain any entries with names not defined by a member of this Tagged type, otherwise an -error must be thrown. Similarly the input object literal or unordered map must +error must be thrown. The input object literal or unordered map must contain exactly one member, otherwise an error must be thrown. The result of coercion is an unordered map with an entry for exactly one member both defined by the tagged type and present in the input. The resulting map is constructed with the following rules: -* If more than one field is defined in the input, an error should be thrown. +* If more than one field is defined in the input, an error must be thrown. * For the single field defined in the input, if the value is {null} and the - field's type is a non-null type, an error should be throw. + field's type is a non-null type, an error must be thrown. * If a literal value is provided for an input object field, an entry in the coerced unordered map is given the result of coercing that value according @@ -1564,7 +1565,7 @@ of rules must be adhered to by every Tagged type in a GraphQL schema. no two members may share the same name. 2. The member must not have a name which begins with the characters {"__"} (two underscores). -3. {IsOutputType(fieldType)} and {IsInputType(type)} cannot both be {false}. +3. {IsOutputType(taggedType)} and {IsInputType(taggedType)} cannot both be {false}. ### Member Deprecation From 55feb1cab004c9c38da0aeaae4f4b4349d111b5b Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 16:32:08 +0100 Subject: [PATCH 07/43] Move TaggedMemberDefinition --- spec/Appendix B -- Grammar Summary.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index dd2dd6a1b..f5c88e33f 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -266,8 +266,6 @@ FieldsDefinition : { FieldDefinition+ } FieldDefinition : Description? Name ArgumentsDefinition? : Type Directives[Const]? -TaggedMemberDefinition : Description? Name : Type Directives[Const]? - ArgumentsDefinition : ( InputValueDefinition+ ) InputValueDefinition : Description? Name : Type DefaultValue? Directives[Const]? @@ -293,6 +291,8 @@ TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedMembers TaggedMembersDefinition : { TaggedMemberDefinition+ } +TaggedMemberDefinition : Description? Name : Type Directives[Const]? + TaggedTypeExtension : - extend tagged Name Directives[Const]? TaggedMembersDefinition - extend tagged Name Directives[Const] From f786f1c52b2327b3c4d67eee9d9e5b111110383b Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 16:32:45 +0100 Subject: [PATCH 08/43] Add TAGGED_MEMBER_DEFINITION directive location --- spec/Appendix B -- Grammar Summary.md | 1 + spec/Section 3 -- Type System.md | 1 + spec/Section 4 -- Introspection.md | 1 + 3 files changed, 3 insertions(+) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index f5c88e33f..d3317c2e4 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -344,6 +344,7 @@ TypeSystemDirectiveLocation : one of `INTERFACE` `UNION` `TAGGED` + `TAGGED_MEMBER_DEFINITION` `ENUM` `ENUM_VALUE` `INPUT_OBJECT` diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index ca676f061..66c252ab6 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -2029,6 +2029,7 @@ TypeSystemDirectiveLocation : one of `INTERFACE` `UNION` `TAGGED` + `TAGGED_MEMBER_DEFINITION` `ENUM` `ENUM_VALUE` `INPUT_OBJECT` diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index f69119984..7a577cdbd 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -220,6 +220,7 @@ enum __DirectiveLocation { INTERFACE UNION TAGGED + TAGGED_MEMBER_DEFINITION ENUM ENUM_VALUE INPUT_OBJECT From 5906cd06540c74071aa8036145c5efd34821d48f Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 16:43:00 +0100 Subject: [PATCH 09/43] Reorder so tagged types comes after interfaces/unions --- 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 a791d3082..0e82f01a6 100644 --- a/spec/Section 2 -- Language.md +++ b/spec/Section 2 -- Language.md @@ -607,7 +607,7 @@ can be used in the context of querying a `User`. Fragments cannot be specified on any input value (scalar, enumeration, or input object). -Fragments can be specified on object types, tagged types, interfaces, and unions. +Fragments can be specified on object types, interfaces, unions, and tagged types. Selections within fragments only return values when the concrete type of the object it is operating on matches the type of the fragment. From bd88e51653d97769aea8ef49583b3b739029b09c Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 16:45:49 +0100 Subject: [PATCH 10/43] Edit out comma that snuck in --- 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 66c252ab6..45e5bc8c5 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -256,7 +256,7 @@ Scalars and Enums form the leaves in response trees; the intermediate levels are `Object` types, which define a set of fields, where each field is another type in the system, allowing the definition of arbitrary type hierarchies. -GraphQL supports two abstract types: interfaces, and unions. +GraphQL supports two abstract types: interfaces and unions. An `Interface` defines a list of fields; `Object` types and other Interface types which implement this Interface are guaranteed to implement those fields. From 514bb7271a56fe4d1e5ad683fde82ecfb2c54c61 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 16:46:15 +0100 Subject: [PATCH 11/43] Add word 'concrete' --- spec/Section 4 -- Introspection.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 7a577cdbd..fe7b5d65f 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -91,8 +91,8 @@ warnings. GraphQL supports type name introspection at any point within a query by the meta-field `__typename: String!` when querying against any Object, Interface, -Union or Tagged. It returns the name of the object or tagged type currently -being queried. +Union or Tagged. It returns the name of the concrete object or tagged type +currently being queried. This is most often used when querying against Interface or Union types to identify which actual type of the possible types has been returned. From 4768d8dbe7d08913a29fefdf93a31a84e80e94de Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 16:58:33 +0100 Subject: [PATCH 12/43] Define member field --- spec/Section 3 -- Type System.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 45e5bc8c5..92da4fd28 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -267,9 +267,10 @@ A `Union` defines a list of possible types; similar to interfaces, whenever the type system claims a union will be returned, one of the possible types will be returned. -A `Tagged Type` defines a list of possible members; whenever the type system -references a tagged type, exactly one of the possible members must be present -and all others must be omitted. +A `Tagged` defines a set of possible member fields, where each member field has +a name and references another type in the system. Whenever the type system +references a tagged type, exactly one of these possible member fields must be +present and all others must be omitted. Finally, oftentimes it is useful to provide complex structs as inputs to GraphQL field arguments or variables; the `Input Object` type allows the schema From 95ccf953196c5ac4c3600452b3d0b1d0527c201a Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 17:00:54 +0100 Subject: [PATCH 13/43] Add Lee's note --- spec/Section 3 -- Type System.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 92da4fd28..167cafcf0 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -932,7 +932,8 @@ May yield the result: ``` The type of an object field argument must be an input type (any type for which -{IsInputType(type)} returns true). +{IsInputType(type)} returns {true}, which includes Object, Interface, Union and +some Tagged types). ### Field Deprecation From 88f983b3855bd0099769b036ed5fe2fbfdf78054 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 17:03:20 +0100 Subject: [PATCH 14/43] Lee's rewording --- spec/Section 3 -- Type System.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 167cafcf0..6ac27a534 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1007,9 +1007,9 @@ objects and interfaces can then implement these interfaces which requires that the implementing type will define all fields defined by those interfaces. Fields on a GraphQL interface have the same rules as fields on a GraphQL -object; their type can be Scalar, Object, Enum, Interface, Union, Tagged types -where {IsOutputType(type)} returns {true}, or any wrapping type whose base type -is one of those six. +object; their type can be any for which {IsOutputType(type)} returns {true}: +Scalar, Object, Enum, Interface, Union, some Tagged types, or any wrapping +types of those. For example, an interface `NamedEntity` may describe a required field and types such as `Person` or `Business` may then implement this interface to guarantee From a6486ce68b5f81c34cd48940c7766c6afdfc31a0 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 17:34:37 +0100 Subject: [PATCH 15/43] Add mutually exclusive tagged type example with distinct types --- spec/Section 3 -- Type System.md | 42 ++++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 6ac27a534..89a39473d 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1394,23 +1394,23 @@ All members defined within a Tagged type must not have a name which begins with {"__"} (two underscores), as this is used exclusively by GraphQL's introspection system. -For example, a type `StringFilter` could be described as: +For example, a type `PackQuantity` could be described as: ```graphql example -tagged StringFilter { - startsWith: String! - contains: String! - lengthAtLeast: Int! +tagged PackQuantity { + volumeInMetresCubed: Float! + numberOfBoxes: Int! + description: String! } ``` In this case we're representing exactly one of the following: -- an object containing a single key `startsWith` with an {String} value -- an object containing a single key `contains` with a {String} value -- an object containing a single key `lengthAtLeast` with an {Int} value +- an object containing a single key `volumeInMetresCubed` with an {Float} value +- an object containing a single key `numberOfBoxes` with a {Int} value +- an object containing a single key `description` with an {String} value -The `StringFilter` Tagged type is valid as both an input and output type, but +The `PackQuantity` Tagged type is valid as both an input and output type, but this is not true of all tagged types. If a Tagged type has a member whose type is only valid for output, then the Tagged type is only valid for output. If a Tagged type has a member whose type is only valid for input, then the Tagged @@ -1423,28 +1423,28 @@ type, an Interface, a Union or another Tagged type. Additionally, it may be any wrapping type (e.g. list, non-null, or any combination thereof) whose underlying base type is one of those six. -Selecting all the members of our `StringFilter` type: +Selecting all the members of our `PackQuantity` type: ```graphql example { - startsWith - contains - lengthAtLeast + volumeInMetresCubed + numberOfBoxes + description } ``` Could yield one of the following objects: -- `{ "startsWith": "GraphQL is" }` -- `{ "contains": "awesome" }` -- `{ "lengthAtLeast": 3 }` +- `{ "volumeInMetresCubed": 3.8 }` +- `{ "numberOfBoxes": 27 }` +- `{ "description": "4 large boxes, 7 medium boxes, and 16 small boxes" }` Valid queries must supply a nested field set for a field that returns a Tagged type, so for this schema: ```graphql example type Query { - stringFilter: StringFilter + packQuantity: PackQuantity } ``` @@ -1452,7 +1452,7 @@ This query is not valid: ```graphql counter-example { - stringFilter + packQuantity } ``` @@ -1460,15 +1460,15 @@ However, this query is valid: ```graphql example { - stringFilter { - contains + packQuantity { + numberOfBoxes } } ``` And may yield one of the following objects: -- `{ "contains": "awesome" }` +- `{ "numberOfBoxes": 27 }` - `{}` **Field Ordering** From cf8d7a6711bfdca134227e1efec91017ca2d4357 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 17:41:11 +0100 Subject: [PATCH 16/43] Make it clear Tagged type fields can be of any type. --- spec/Section 3 -- Type System.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 89a39473d..45752f3e6 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1418,10 +1418,10 @@ type is only valid for input. If a Tagged type has a member that is only valid for input, and another member that's only valid for output, then it is not a valid Tagged type. -A member of a Tagged type may be a Scalar, Enum, Input Object type, Object -type, an Interface, a Union or another Tagged type. Additionally, it may be -any wrapping type (e.g. list, non-null, or any combination thereof) whose -underlying base type is one of those six. +A member of a Tagged type may be of any valid GraphQL type, including Scalar, +Enum, Input Object type, Object type, an Interface, a Union, another Tagged +type, or any wrapping type (e.g. list, non-null, or any combination thereof) +whose underlying base type is one of those six. Selecting all the members of our `PackQuantity` type: From a09b30a98c990bfcb3bb43449fbf73fee3920ee9 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 17:43:09 +0100 Subject: [PATCH 17/43] s/objects/results --- spec/Section 3 -- Type System.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 45752f3e6..90ba3fcf7 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1433,7 +1433,7 @@ Selecting all the members of our `PackQuantity` type: } ``` -Could yield one of the following objects: +Could yield one of the following results: - `{ "volumeInMetresCubed": 3.8 }` - `{ "numberOfBoxes": 27 }` @@ -1466,7 +1466,7 @@ However, this query is valid: } ``` -And may yield one of the following objects: +And may yield one of the following results: - `{ "numberOfBoxes": 27 }` - `{}` From c81ebb1f600c4000356e3dace0f2996d43a256e0 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 17:43:47 +0100 Subject: [PATCH 18/43] : --- 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 90ba3fcf7..9b56339ba 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1486,7 +1486,7 @@ using the {CollectFields()} algorithm. Determining the result of coercing an object or tagged type is the heart of the GraphQL executor, so this is covered in that section of the spec. -Note only Tagged types for which {IsOutputType(type)} returns {true} may be +Note: only Tagged types for which {IsOutputType(type)} returns {true} may be used in output. **Input Coercion** From 5dcb81bcda794e3e8d2ea3b5e7623147e98cf423 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 17:48:01 +0100 Subject: [PATCH 19/43] Checking for one key is easier than validating the given keys (maybe) --- 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 9b56339ba..d6e605a31 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1552,7 +1552,7 @@ Literal Value | Variables | Coerced Value `$var` | `{ var: { a: "abc" } }` | `{ a: "abc" }` `{ a: "abc", b: null }` | `{}` | Error: Exactly one key must be specified `{ b: $var }` | `{ var: null }` | Error: {b} must be non-null. -`{ b: 123, c: "xyz" }` | `{}` | Error: Unexpected member {c} +`{ b: 123, c: "xyz" }` | `{}` | Error: Exactly one key must be specified From 71ca89219cd3a26a552514f4dcbe8ef07b29ad49 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 17:48:22 +0100 Subject: [PATCH 20/43] nit --- spec/Section 3 -- Type System.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index d6e605a31..b488bb4c1 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1554,8 +1554,6 @@ Literal Value | Variables | Coerced Value `{ b: $var }` | `{ var: null }` | Error: {b} must be non-null. `{ b: 123, c: "xyz" }` | `{}` | Error: Exactly one key must be specified - - **Type Validation** Tagged types have the potential to be invalid if incorrectly defined. This set From ce213bbd18eb0e5dcc2df21f9bd3666a1e351440 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 17:51:02 +0100 Subject: [PATCH 21/43] Allow @deprecated on TAGGED_MEMBER_DEFINITION --- 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 b488bb4c1..c7d757eb6 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -2189,12 +2189,13 @@ must *not* be queried if either the `@skip` condition is true *or* the ```graphql directive @deprecated( reason: String = "No longer supported" -) on FIELD_DEFINITION | ENUM_VALUE +) on FIELD_DEFINITION | ENUM_VALUE | TAGGED_MEMBER_DEFINITION ``` The `@deprecated` directive is used within the type system definition language to indicate deprecated portions of a GraphQL service's schema, such as -deprecated fields on a type or deprecated enum values. +deprecated fields on a type, deprecated enum values or deprecated tagged member +fields. Deprecations include a reason for why it is deprecated, which is formatted using Markdown syntax (as specified by [CommonMark](https://commonmark.org/)). From 7ac75324f0d33e71b7e1c9d5a8d2e23158a366a1 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 17:54:52 +0100 Subject: [PATCH 22/43] Reword note on tagged member deprecation --- spec/Section 3 -- Type System.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index c7d757eb6..cbbd0520e 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1584,8 +1584,8 @@ tagged ExampleType { } ``` -Note: a deprecated member of a tagged type may legitimately be marked as -non-nullable since this member need not be supplied/returned. +Note: due to the nature of tagged types, it is valid to mark a member of a +tagged type deprecated even when that member is non-nullable. ### Tagged Extensions From 0ae9ba8d1e1373e6805831cb12ff68b44b25d90a Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 18:00:42 +0100 Subject: [PATCH 23/43] Add note that added members must not make the tagged type invalid --- spec/Section 3 -- Type System.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index cbbd0520e..d8bab19d6 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1628,7 +1628,10 @@ Tagged type extensions have the potential to be invalid if incorrectly defined. {"__"}. 4. Any members of a Tagged type extension must not be already defined on the original Tagged type. -5. Any non-repeatable directives provided must not already apply to the +5. Members of a Tagged type extension must not cause + {IsOutputType(taggedType)} and {IsInputType(taggedType)} to both become + {false}. +6. Any non-repeatable directives provided must not already apply to the original Tagged type. From 361a235dfe5c85df07e1a4d4fd953f1230d087be Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 18:01:35 +0100 Subject: [PATCH 24/43] Fix case --- 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 d8bab19d6..88b9f1bd2 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1716,7 +1716,7 @@ Fields may accept arguments to configure their behavior. These inputs are often scalars or enums, but they sometimes need to represent more complex values. A GraphQL Input Object defines a set of input fields; the input fields are -either scalars, enums, other input objects, or Tagged types for which +either scalars, enums, other input objects, or tagged types for which {IsInputType(type)} returns {true}. This allows arguments to accept arbitrarily complex structs. From a1705b5b1e6c158513d3de841585974d73e920c1 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 18:02:00 +0100 Subject: [PATCH 25/43] Remove duplicate --- spec/Section 3 -- Type System.md | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 88b9f1bd2..68cce6710 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -2037,7 +2037,6 @@ TypeSystemDirectiveLocation : one of `ENUM_VALUE` `INPUT_OBJECT` `INPUT_FIELD_DEFINITION` - `TAGGED` A GraphQL schema describes directives which are used to annotate various parts of a GraphQL document as an indicator that they should be evaluated differently From 8807c1c3b6ec21f0eee7d59213a43e068d159105 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 18:05:35 +0100 Subject: [PATCH 26/43] Reword to follow Lee's example --- spec/Section 4 -- Introspection.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index fe7b5d65f..3483a4ef5 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -90,9 +90,9 @@ warnings. ## Type Name Introspection GraphQL supports type name introspection at any point within a query by the -meta-field `__typename: String!` when querying against any Object, Interface, -Union or Tagged. It returns the name of the concrete object or tagged type -currently being queried. +meta-field __typename: String! when querying against any Object, Interface, +Union or Tagged types. It returns the name of the concrete type currently being +queried, which will be an Object or Tagged type. This is most often used when querying against Interface or Union types to identify which actual type of the possible types has been returned. From 61b7b8f506d05a4db05dafa9130d4a32c2183aaa Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 18:08:03 +0100 Subject: [PATCH 27/43] Reposition TAGGED to always be between Union and Enum. --- spec/Section 4 -- Introspection.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 3483a4ef5..516ad66d9 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -140,15 +140,15 @@ type __Type { # should be non-null for INTERFACE and UNION only, always null for the others possibleTypes: [__Type!] + # should be non-null for TAGGED only, must be null for the others + members(includeDeprecated: Boolean = false): [__Member!] + # should be non-null for ENUM only, must be null for the others enumValues(includeDeprecated: Boolean = false): [__EnumValue!] # should be non-null for INPUT_OBJECT only, must be null for the others inputFields: [__InputValue!] - # should be non-null for TAGGED only, must be null for the others - members(includeDeprecated: Boolean = false): [__Member!] - # should be non-null for NON_NULL and LIST only, must be null for the others ofType: __Type } @@ -189,9 +189,9 @@ enum __TypeKind { OBJECT INTERFACE UNION + TAGGED ENUM INPUT_OBJECT - TAGGED LIST NON_NULL } From 285673a5ab4fbf20ed87b6af929d95b6a4113f0c Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 18:10:46 +0100 Subject: [PATCH 28/43] Terran -> Earthling --- spec/Section 5 -- Validation.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index cdb5f03f4..cd1612002 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -89,7 +89,7 @@ tagged Being { alien: Alien } -tagged Terran { +tagged Earthling { cat: Cat dog: Dog human: Human @@ -1186,7 +1186,7 @@ and the following is invalid ```graphql counter-example fragment beingFragment on Being { - ... on Terran { + ... on Earthling { dog { barkVolume } @@ -1194,11 +1194,11 @@ fragment beingFragment on Being { } ``` -This counter-example may be surprising since Being is covariant to Terran -(Being contains all the members Terran contains), however allowing this spread -to be valid would inhibit schema evolution - we'd have to ensure that Being -always remained covariant to Terran, preventing us from adding members to Terran -alone without adding them to Being. +This counter-example may be surprising since Being is covariant to Earthling +(Being contains all the members Earthling contains), however allowing this +spread to be valid would inhibit schema evolution - we'd have to ensure that +Being always remained covariant to Earthling, preventing us from adding members +to Earthling alone without adding them to Being. ##### Abstract Spreads in Object Scope From d9820c43b62067b9eef36efc79ebfcd44778365b Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 18:12:03 +0100 Subject: [PATCH 29/43] Make header consistent --- spec/Section 5 -- Validation.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index cd1612002..8000244df 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -337,8 +337,9 @@ must provide the operation name as described in {GetOperation()}. ## Fields -### Field Selections on Objects, Interfaces, Unions and Tagged Types +### Field Selections +Field Selections must exist on Objects, Interfaces, Unions and Tagged Types **Formal Specification** From f91d8ba6302ffb4cf107cc2c94c73695f1011b60 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 18:13:34 +0100 Subject: [PATCH 30/43] __Type represents all named types in the system --- spec/Section 4 -- Introspection.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 516ad66d9..ff5ce7a52 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -231,9 +231,8 @@ enum __DirectiveLocation { ### The __Type Type -`__Type` is at the core of the type introspection system. It represents -scalars, interfaces, object types, unions, enums, input object types and tagged -types in the system. +`__Type` is at the core of the type introspection system. It represents all +named types in the system. `__Type` also represents type modifiers, which are used to modify a type that it refers to (`ofType: __Type`). This is how we represent lists, From 82151ba768e97b628570fe27b5b5bb180b373c99 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 18:15:33 +0100 Subject: [PATCH 31/43] Apply Lee's suggestion --- spec/Section 5 -- Validation.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 8000244df..494eab96b 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -341,6 +341,8 @@ must provide the operation name as described in {GetOperation()}. Field Selections must exist on Objects, Interfaces, Unions and Tagged Types +Note: Tagged types define members which are queried with field selections. + **Formal Specification** * For each {selection} in the document. @@ -412,11 +414,6 @@ fragment directFieldSelectionOnUnion on CatOrDog { } ``` -Tagged types define "members", but for the purposes of this section the members -fill the same role as fields of an object type or interface. - -Note: how best to phrase this/factor it in? - ### Field Selection Merging **Formal Specification** From 02a2a93b1f640792cbfff7c45e287e34fa9f3f6b Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 18:16:15 +0100 Subject: [PATCH 32/43] Combine --- 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 494eab96b..88b9108c8 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -1121,8 +1121,7 @@ fragment ownerFragment on Human { GetPossibleTypes(type): - * If {type} is an object type, return a set containing {type} - * If {type} is a tagged type, return a set containing {type} + * If {type} is an object type or tagged type, return a set containing {type} * If {type} is an interface type, return the set of types implementing {type} * If {type} is a union type, return the set of possible types of {type} From 5bdcc300ac87d1dead525bb11819a9fa8e536b05 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Tue, 1 Sep 2020 18:52:40 +0100 Subject: [PATCH 33/43] Use 'field' rather than 'key' --- spec/Section 6 -- Execution.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index be4ced56e..cb6be7350 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -316,9 +316,6 @@ To execute a selection set, the object value being evaluated and the object type need to be known, as well as whether it must be executed serially, or may be executed in parallel. -For the purposes of execution, the term "field" may be used to reference a -"member" of a tagged type; this simplifies the wording of the algorithm. - First, the selection set is turned into a grouped field set; then, each represented field in the grouped field set produces an entry into a response map. @@ -326,12 +323,11 @@ response map. GetTaggedMemberName(objectType, objectValue): * If {objectType} is not a Tagged type, return {null}. - * Let {keys} be the keys of {objectValue} that are member names of {objectType}. - * If the length of {keys} is not 1: + * Let {fields} be the fields of {objectValue} that are member names of {objectType}. + * If the length of {fields} is not 1: * Throw a field error. - * Let {key} be the first entry in {keys} - * Return {key}. - + * Let {memberName} be the first entry in {fields} + * Return {memberName}. ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues): From 88e506ac92e35861c1218408ab7fda79b19054f1 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 2 Sep 2020 08:47:59 +0100 Subject: [PATCH 34/43] GetTaggedMember[Field]Name --- 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 cb6be7350..ebb9454b3 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -320,7 +320,7 @@ First, the selection set is turned into a grouped field set; then, each represented field in the grouped field set produces an entry into a response map. -GetTaggedMemberName(objectType, objectValue): +GetTaggedMemberFieldName(objectType, objectValue): * If {objectType} is not a Tagged type, return {null}. * Let {fields} be the fields of {objectValue} that are member names of {objectType}. @@ -331,7 +331,7 @@ GetTaggedMemberName(objectType, objectValue): ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues): - * Let {taggedMemberName} be {GetTaggedMemberName}(objectType, objectValue)}. + * Let {taggedMemberName} be {GetTaggedMemberFieldName}(objectType, objectValue)}. * Let {groupedFieldSet} be the result of {CollectFields(objectType, selectionSet, variableValues, taggedMemberName)}. * Initialize {resultMap} to an empty ordered map. From fee5ca3a1112d49105d717e4ec4e1e36e36986a9 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 2 Sep 2020 08:48:12 +0100 Subject: [PATCH 35/43] __[Tagged]Member[Field] --- spec/Section 4 -- Introspection.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index ff5ce7a52..3fc308f19 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -141,7 +141,7 @@ type __Type { possibleTypes: [__Type!] # should be non-null for TAGGED only, must be null for the others - members(includeDeprecated: Boolean = false): [__Member!] + members(includeDeprecated: Boolean = false): [__TaggedMemberField!] # should be non-null for ENUM only, must be null for the others enumValues(includeDeprecated: Boolean = false): [__EnumValue!] @@ -169,7 +169,7 @@ type __InputValue { defaultValue: String } -type __Member { +type __TaggedMemberField { name: String! description: String type: __Type! @@ -429,9 +429,9 @@ Fields default value used by this input value in the condition a value is not provided at runtime. If this input value has no default value, returns {null}. -### The __Member Type +### The __TaggedMemberField Type -The `__Member` type represents `members` of a tagged type. +The `__TaggedMemberField` type represents `members` of a tagged type. Fields From 0f5d7cc0bd1d31adbf6e94c38b400af95cf806ba Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 2 Sep 2020 08:49:03 +0100 Subject: [PATCH 36/43] TAGGED_MEMBER_[FIELD_]DEFINITION --- spec/Appendix B -- Grammar Summary.md | 2 +- spec/Section 3 -- Type System.md | 4 ++-- spec/Section 4 -- Introspection.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index d3317c2e4..bb6e83fa5 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -344,7 +344,7 @@ TypeSystemDirectiveLocation : one of `INTERFACE` `UNION` `TAGGED` - `TAGGED_MEMBER_DEFINITION` + `TAGGED_MEMBER_FIELD_DEFINITION` `ENUM` `ENUM_VALUE` `INPUT_OBJECT` diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 68cce6710..e76cae2a7 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -2032,7 +2032,7 @@ TypeSystemDirectiveLocation : one of `INTERFACE` `UNION` `TAGGED` - `TAGGED_MEMBER_DEFINITION` + `TAGGED_MEMBER_FIELD_DEFINITION` `ENUM` `ENUM_VALUE` `INPUT_OBJECT` @@ -2191,7 +2191,7 @@ must *not* be queried if either the `@skip` condition is true *or* the ```graphql directive @deprecated( reason: String = "No longer supported" -) on FIELD_DEFINITION | ENUM_VALUE | TAGGED_MEMBER_DEFINITION +) on FIELD_DEFINITION | ENUM_VALUE | TAGGED_MEMBER_FIELD_DEFINITION ``` The `@deprecated` directive is used within the type system definition language diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 3fc308f19..33d387177 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -220,7 +220,7 @@ enum __DirectiveLocation { INTERFACE UNION TAGGED - TAGGED_MEMBER_DEFINITION + TAGGED_MEMBER_FIELD_DEFINITION ENUM ENUM_VALUE INPUT_OBJECT From 94a67b73a26136011cca25f8918996603c7f5a1e Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 2 Sep 2020 08:51:18 +0100 Subject: [PATCH 37/43] TaggedMember[Field]Definition/TaggedMember[Field]sDefinition --- spec/Appendix B -- Grammar Summary.md | 8 ++++---- spec/Section 3 -- Type System.md | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index bb6e83fa5..c041f8d31 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -287,14 +287,14 @@ UnionTypeExtension : - extend union Name Directives[Const]? UnionMemberTypes - extend union Name Directives[Const] -TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedMembersDefinition? +TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedMemberFieldsDefinition? -TaggedMembersDefinition : { TaggedMemberDefinition+ } +TaggedMemberFieldsDefinition : { TaggedMemberFieldDefinition+ } -TaggedMemberDefinition : Description? Name : Type Directives[Const]? +TaggedMemberFieldDefinition : Description? Name : Type Directives[Const]? TaggedTypeExtension : - - extend tagged Name Directives[Const]? TaggedMembersDefinition + - extend tagged Name Directives[Const]? TaggedMemberFieldsDefinition - extend tagged Name Directives[Const] EnumTypeDefinition : Description? enum Name Directives[Const]? EnumValuesDefinition? diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index e76cae2a7..8e4509f21 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -1370,15 +1370,15 @@ Union type extensions have the potential to be invalid if incorrectly defined. ## Tagged Types -TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedMembersDefinition? +TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedMemberFieldsDefinition? TaggedTypeExtension : - - extend tagged Name Directives[Const]? TaggedMembersDefinition + - extend tagged Name Directives[Const]? TaggedMemberFieldsDefinition - extend tagged Name Directives[Const] -TaggedMembersDefinition : { TaggedMemberDefinition+ } +TaggedMemberFieldsDefinition : { TaggedMemberFieldDefinition+ } -TaggedMemberDefinition : Description? Name : Type Directives[Const]? +TaggedMemberFieldDefinition : Description? Name : Type Directives[Const]? Tagged types represent a list of possible named members, exactly one of which must be present. This resulting member must yield a value of a specific type. @@ -1590,7 +1590,7 @@ tagged type deprecated even when that member is non-nullable. ### Tagged Extensions TaggedTypeExtension : - - extend tagged Name Directives[Const]? TaggedMembersDefinition + - extend tagged Name Directives[Const]? TaggedMemberFieldsDefinition - extend tagged Name Directives[Const] Tagged type extensions are used to represent a Tagged type which has been From f66e237789e560998086b95b16febbcc09b39cec Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 2 Sep 2020 08:52:43 +0100 Subject: [PATCH 38/43] taggedMember[Field]Name --- spec/Section 6 -- Execution.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index ebb9454b3..ff76a28f3 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -331,9 +331,9 @@ GetTaggedMemberFieldName(objectType, objectValue): ExecuteSelectionSet(selectionSet, objectType, objectValue, variableValues): - * Let {taggedMemberName} be {GetTaggedMemberFieldName}(objectType, objectValue)}. + * Let {taggedMemberFieldName} be {GetTaggedMemberFieldName}(objectType, objectValue)}. * Let {groupedFieldSet} be the result of - {CollectFields(objectType, selectionSet, variableValues, taggedMemberName)}. + {CollectFields(objectType, selectionSet, variableValues, taggedMemberFieldName)}. * Initialize {resultMap} to an empty ordered map. * For each {groupedFieldSet} as {responseKey} and {fields}: * Let {fieldName} be the name of the first entry in {fields}. @@ -487,9 +487,9 @@ 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, taggedMemberName, visitedFragments): +CollectFields(objectType, selectionSet, variableValues, taggedMemberFieldName, visitedFragments): - * If {taggedMemberName} is not provided, initialize it to {null}. + * If {taggedMemberFieldName} is not provided, initialize it to {null}. * If {visitedFragments} is not provided, initialize it to the empty set. * Initialize {groupedFields} to an empty ordered map of lists. * For each {selection} in {selectionSet}: @@ -501,8 +501,8 @@ CollectFields(objectType, selectionSet, variableValues, taggedMemberName, visite {selection} in {selectionSet}. * If {selection} is a {Field}: * Let {fieldName} be the field name. - * If {taggedMemberName} is not null: - * If {fieldName} is not {taggedMemberName} and {fieldName} is not an introspection field (beginning with the characters {"__"} (two underscores)): + * If {taggedMemberFieldName} is not null: + * If {fieldName} is not {taggedMemberFieldName} and {fieldName} is not an introspection field (beginning with the characters {"__"} (two underscores)): * Continue with the next {selection} in {selectionSet}. * Let {responseKey} be the response key of {selection} (the alias if defined, otherwise the field name). * Let {groupForResponseKey} be the list in {groupedFields} for @@ -522,7 +522,7 @@ CollectFields(objectType, selectionSet, variableValues, taggedMemberName, visite with the next {selection} in {selectionSet}. * Let {fragmentSelectionSet} be the top-level selection set of {fragment}. * Let {fragmentGroupedFieldSet} be the result of calling - {CollectFields(objectType, fragmentSelectionSet, variableValues, taggedMemberName, visitedFragments)}. + {CollectFields(objectType, fragmentSelectionSet, variableValues, taggedMemberFieldName, visitedFragments)}. * For each {fragmentGroup} in {fragmentGroupedFieldSet}: * Let {responseKey} be the response key shared by all fields in {fragmentGroup}. * Let {groupForResponseKey} be the list in {groupedFields} for @@ -533,7 +533,7 @@ CollectFields(objectType, selectionSet, variableValues, taggedMemberName, visite * If {fragmentType} is not {null} and {DoesFragmentTypeApply(objectType, 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 {CollectFields(objectType, fragmentSelectionSet, variableValues, taggedMemberName, visitedFragments)}. + * Let {fragmentGroupedFieldSet} be the result of calling {CollectFields(objectType, fragmentSelectionSet, variableValues, taggedMemberFieldName, visitedFragments)}. * For each {fragmentGroup} in {fragmentGroupedFieldSet}: * Let {responseKey} be the response key shared by all fields in {fragmentGroup}. * Let {groupForResponseKey} be the list in {groupedFields} for From e3082583524e64951d48b751809e56172e267e87 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 2 Sep 2020 09:23:10 +0100 Subject: [PATCH 39/43] Fix definition ordering --- spec/Section 3 -- Type System.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 8e4509f21..c7f0164d6 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -240,9 +240,9 @@ TypeDefinition : - ObjectTypeDefinition - InterfaceTypeDefinition - UnionTypeDefinition + - TaggedTypeDefinition - EnumTypeDefinition - InputObjectTypeDefinition - - TaggedTypeDefinition The fundamental unit of any GraphQL Schema is the type. There are six kinds of named type definitions in GraphQL, and two wrapping types. @@ -339,9 +339,9 @@ TypeExtension : - ObjectTypeExtension - InterfaceTypeExtension - UnionTypeExtension + - TaggedTypeExtension - EnumTypeExtension - InputObjectTypeExtension - - TaggedTypeExtension Type extensions are used to represent a GraphQL type which has been extended from some original type. For example, this might be used by a local service to From 6fa34c2ba628bee89306eaf4ed1a82b08d2344c3 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 2 Sep 2020 10:43:59 +0100 Subject: [PATCH 40/43] Members -> member fields --- spec/Section 3 -- Type System.md | 133 +++++++++++++++-------------- spec/Section 4 -- Introspection.md | 21 ++--- spec/Section 5 -- Validation.md | 11 +-- 3 files changed, 84 insertions(+), 81 deletions(-) diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index c7f0164d6..b3e9e0aca 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -304,8 +304,8 @@ other kinds of types can only be used in one or the other. Input Object types ca 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. Tagged types may be used -as input types or output types depending on how the types of their members may -be used. +as input types or output types depending on how the types of their member +fields may be used. IsInputType(type) : * If {type} is a List type or Non-Null type: @@ -314,7 +314,7 @@ IsInputType(type) : * If {type} is a Scalar, Enum, or Input Object type: * Return {true} * If {type} is a Tagged type: - * If there exists a member in {type} where {IsInputType(memberType)} is {false} + * If there exists a member field in {type} where {IsInputType(memberFieldType)} is {false} * Return {false} * Return {true} * Return {false} @@ -326,7 +326,7 @@ IsOutputType(type) : * If {type} is a Scalar, Object, Interface, Union, or Enum type: * Return {true} * If {type} is a Tagged type: - * If there exists a member in {type} where {IsOutputType(memberType)} is {false} + * If there exists a member field in {type} where {IsOutputType(memberFieldType)} is {false} * Return {false} * Return {true} * Return {false} @@ -1380,18 +1380,18 @@ TaggedMemberFieldsDefinition : { TaggedMemberFieldDefinition+ } TaggedMemberFieldDefinition : Description? Name : Type Directives[Const]? -Tagged types represent a list of possible named members, exactly one of which -must be present. This resulting member must yield a value of a specific type. -In outputs, like for Objects, Tagged types should be serialized as ordered -maps: the queried member names (or aliases) that are present are the keys and -the result of evaluating the member is the value, ordered by the order in which -they appear in the query. Note that Tagged types may be objects with more than -one key even though they guarantee that exactly one member is present, this is -because aliases may be used, and `__typename` may also be queried (and will -return the name of the tagged type). - -All members defined within a Tagged type must not have a name which begins with -{"__"} (two underscores), as this is used exclusively by GraphQL's +Tagged types represent a list of possible named member fields, exactly one of +which must be present. This resulting member field must yield a value of a +specific type. In outputs, like for Objects, Tagged types should be serialized +as ordered maps: the queried member field names (or aliases) that are present +are the keys and the result of evaluating the member field is the value, +ordered by the order in which they appear in the query. Note that Tagged types +may be objects with more than one key even though they guarantee that exactly +one member field is present, this is because aliases may be used, and +`__typename` may also be queried (and will return the name of the tagged type). + +All member fields defined within a Tagged type must not have a name which +begins with {"__"} (two underscores), as this is used exclusively by GraphQL's introspection system. For example, a type `PackQuantity` could be described as: @@ -1411,19 +1411,19 @@ In this case we're representing exactly one of the following: - an object containing a single key `description` with an {String} value The `PackQuantity` Tagged type is valid as both an input and output type, but -this is not true of all tagged types. If a Tagged type has a member whose type -is only valid for output, then the Tagged type is only valid for output. If a -Tagged type has a member whose type is only valid for input, then the Tagged -type is only valid for input. If a Tagged type has a member that is only valid -for input, and another member that's only valid for output, then it is not a -valid Tagged type. - -A member of a Tagged type may be of any valid GraphQL type, including Scalar, -Enum, Input Object type, Object type, an Interface, a Union, another Tagged -type, or any wrapping type (e.g. list, non-null, or any combination thereof) -whose underlying base type is one of those six. +this is not true of all tagged types. If a Tagged type has a member field whose +type is only valid for output, then the Tagged type is only valid for output. +If a Tagged type has a member field whose type is only valid for input, then +the Tagged type is only valid for input. If a Tagged type has a member field +that is only valid for input, and another member field that's only valid for +output, then it is not a valid Tagged type. -Selecting all the members of our `PackQuantity` type: +A member field of a Tagged type may be of any valid GraphQL type, including +Scalar, Enum, Input Object type, Object type, an Interface, a Union, another +Tagged type, or any wrapping type (e.g. list, non-null, or any combination +thereof) whose underlying base type is one of those seven. + +Selecting all the member fields of our `PackQuantity` type: ```graphql example { @@ -1477,8 +1477,8 @@ Like with Object types, when querying a Tagged type, the resulting mapping of fields are conceptually ordered in the same order in which they were encountered during query execution, excluding fragments for which the type does not apply and fields or fragments that are skipped via `@skip` or `@include` -directives, and fields which are neither the single matched member nor the -`__typename` introspection field. This ordering is correctly produced when +directives, and fields which are neither the single matched member field nor +the `__typename` introspection field. This ordering is correctly produced when using the {CollectFields()} algorithm. **Result Coercion** @@ -1497,12 +1497,12 @@ input. The value for an input Tagged type should be an input object literal or an unordered map supplied by a variable, otherwise a query error must be thrown. In either case, the input object literal or unordered map must not contain any -entries with names not defined by a member of this Tagged type, otherwise an -error must be thrown. The input object literal or unordered map must -contain exactly one member, otherwise an error must be thrown. +entries with names not defined by a member field of this Tagged type, otherwise +an error must be thrown. The input object literal or unordered map must contain +exactly one member field, otherwise an error must be thrown. -The result of coercion is an unordered map with an entry for exactly one -member both defined by the tagged type and present in the input. The resulting +The result of coercion is an unordered map with an entry for exactly one member +field both defined by the tagged type and present in the input. The resulting map is constructed with the following rules: * If more than one field is defined in the input, an error must be thrown. @@ -1512,20 +1512,21 @@ map is constructed with the following rules: * If a literal value is provided for an input object field, an entry in the coerced unordered map is given the result of coercing that value according - to the input coercion rules for the type of the tagged member for that field. + to the input coercion rules for the type of the tagged member field for that + field. * If a variable is provided for an input object field, the runtime value of that variable must be used. If the runtime value is {null} and the tagged member - type is non-null, a field error must be thrown. If no runtime value is + field type is non-null, a field error must be thrown. If no runtime value is provided, the variable definition's default value should be used. If the variable definition does not provide a default value, the field should be treated as if it was not specified. -* No entry should be added to the unordered map for any other members defined - on the Tagged type. +* No entry should be added to the unordered map for any other member fields + defined on the Tagged type. -Following are examples of input coercion for a Tagged type with a -`String` member `a` and a required (non-null) `Int!` member `b`: +Following are examples of input coercion for a Tagged type with a `String` +member field `a` and a required (non-null) `Int!` member field `b`: ```graphql example tagged ExampleInputTagged { @@ -1546,7 +1547,7 @@ Literal Value | Variables | Coerced Value `"abc123"` | `{}` | Error: Incorrect value `$var` | `{ var: "abc123" } }` | Error: Incorrect value `{ a: "abc", b: "123" }` | `{}` | Error: Exactly one key must be specified -`{ b: "123" }` | `{}` | Error: Incorrect value for member {b} +`{ b: "123" }` | `{}` | Error: Incorrect value for member field {b} `{ a: "abc" }` | `{}` | `{ a: "abc" }` `{ b: $var }` | `{}` | Error: No keys were specified `$var` | `{ var: { a: "abc" } }` | `{ a: "abc" }` @@ -1559,21 +1560,21 @@ Literal Value | Variables | Coerced Value Tagged types have the potential to be invalid if incorrectly defined. This set of rules must be adhered to by every Tagged type in a GraphQL schema. -1. A Tagged type must define one or more members. -2. For each member of a Tagged type: - 1. The member must have a unique name within that Tagged type; - no two members may share the same name. - 2. The member must not have a name which begins with the - characters {"__"} (two underscores). +1. A Tagged type must define one or more member fields. +2. For each member field of a Tagged type: + 1. The member field must have a unique name within that Tagged type; no two + member fields may share the same name. + 2. The member field must not have a name which begins with the characters + {"__"} (two underscores). 3. {IsOutputType(taggedType)} and {IsInputType(taggedType)} cannot both be {false}. -### Member Deprecation +### Member Field Deprecation -Members in a Tagged type may be marked as deprecated as deemed necessary by -the application. It is still legal to query for these members on outputs or -supply these members in inputs (to ensure existing clients are not broken by -the change), but the members should be appropriately treated in documentation -and tooling. +Member fields in a Tagged type may be marked as deprecated as deemed necessary +by the application. It is still legal to query for these member fields on +outputs or supply these member fields in inputs (to ensure existing clients are +not broken by the change), but the member fields should be appropriately +treated in documentation and tooling. When using the type system definition language, `@deprecated` directives are used to indicate that a field is deprecated: @@ -1584,8 +1585,8 @@ tagged ExampleType { } ``` -Note: due to the nature of tagged types, it is valid to mark a member of a -tagged type deprecated even when that member is non-nullable. +Note: due to the nature of tagged types, it is valid to mark a member field of +a tagged type deprecated even when that member field is non-nullable. ### Tagged Extensions @@ -1607,11 +1608,11 @@ extend tagged StringFilter { } ``` -Tagged type extensions may choose not to add additional members, instead only -adding directives. +Tagged type extensions may choose not to add additional member fields, instead +only adding directives. In this example, a directive is added to a `StringFilter` type without adding -members: +member fields: ```graphql example extend tagged StringFilter @addedDirective @@ -1622,13 +1623,13 @@ extend tagged StringFilter @addedDirective Tagged type extensions have the potential to be invalid if incorrectly defined. 1. The named type must already be defined and must be a Tagged type. -2. The members of a Tagged type extension must have unique names; no two members - may share the same name. -3. The members of a Tagged type extension must not have names beginning with - {"__"}. -4. Any members of a Tagged type extension must not be already defined on the - original Tagged type. -5. Members of a Tagged type extension must not cause +2. The member fields of a Tagged type extension must have unique names; no two + member fields may share the same name. +3. The member fields of a Tagged type extension must not have names beginning + with {"__"}. +4. Any member fields of a Tagged type extension must not be already defined on + the original Tagged type. +5. Member fields of a Tagged type extension must not cause {IsOutputType(taggedType)} and {IsInputType(taggedType)} to both become {false}. 6. Any non-repeatable directives provided must not already apply to the diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 33d387177..9d9e0f00c 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -141,7 +141,7 @@ type __Type { possibleTypes: [__Type!] # should be non-null for TAGGED only, must be null for the others - members(includeDeprecated: Boolean = false): [__TaggedMemberField!] + memberFields(includeDeprecated: Boolean = false): [__TaggedMemberField!] # should be non-null for ENUM only, must be null for the others enumValues(includeDeprecated: Boolean = false): [__EnumValue!] @@ -317,18 +317,19 @@ Fields #### Tagged Tagged types are an type where exactly one member out of a list of potential -members must be present. The possible members of a tagged type are explicitly -listed out in `members`. No modification of a type is necessary to use it as -the member type of a tagged type. +members must be present. The possible members of a tagged type are modeled as +fields - a name and associated type - and are explicitly listed out in +`memberFields`. No modification of a type is necessary to use it as the member +field type of a tagged type. Fields * `kind` must return `__TypeKind.TAGGED`. * `name` must return a String. * `description` may return a String or {null}. -* `members`: The set of members query-able on this type. +* `memberFields`: The set of member fields query-able on this type. * Accepts the argument `includeDeprecated` which defaults to {false}. If - {true}, deprecated members are also returned. + {true}, deprecated member fields are also returned. * All other fields must return {null}. @@ -431,16 +432,16 @@ Fields ### The __TaggedMemberField Type -The `__TaggedMemberField` type represents `members` of a tagged type. +The `__TaggedMemberField` type represents `memberFields` of a tagged type. Fields * `name` must return a String * `description` may return a String or {null} -* `type` must return a `__Type` that represents the type this member expects. -* `isDeprecated` returns {true} if this member should no longer be used, +* `type` must return a `__Type` that represents the type this member field expects. +* `isDeprecated` returns {true} if this member field should no longer be used, otherwise {false}. -* `deprecationReason` optionally provides a reason why this member is deprecated. +* `deprecationReason` optionally provides a reason why this member field is deprecated. ### The __EnumValue Type diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 88b9108c8..f10b34951 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -341,7 +341,8 @@ must provide the operation name as described in {GetOperation()}. Field Selections must exist on Objects, Interfaces, Unions and Tagged Types -Note: Tagged types define members which are queried with field selections. +Note: Tagged types define member fields which are queried with field +selections. **Formal Specification** @@ -1192,10 +1193,10 @@ fragment beingFragment on Being { ``` This counter-example may be surprising since Being is covariant to Earthling -(Being contains all the members Earthling contains), however allowing this -spread to be valid would inhibit schema evolution - we'd have to ensure that -Being always remained covariant to Earthling, preventing us from adding members -to Earthling alone without adding them to Being. +(Being contains all the member fields Earthling contains), however allowing +this spread to be valid would inhibit schema evolution - we'd have to ensure +that Being always remained covariant to Earthling, preventing us from adding +member fields to Earthling alone without adding them to Being. ##### Abstract Spreads in Object Scope From 4bf67b417db45f9cf805f3003c5c8509294e3667 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 2 Sep 2020 10:56:31 +0100 Subject: [PATCH 41/43] Grammar --- 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 f10b34951..2d944525d 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -339,7 +339,7 @@ must provide the operation name as described in {GetOperation()}. ### Field Selections -Field Selections must exist on Objects, Interfaces, Unions and Tagged Types +Field Selections must exist on Object, Interface, Union and Tagged types. Note: Tagged types define member fields which are queried with field selections. From 5afea0bd83145b4de07e9b10c040bcd4032d6cc9 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Wed, 2 Sep 2020 10:58:26 +0100 Subject: [PATCH 42/43] Fix incorrect capital --- 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 2d944525d..9c0601161 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -339,7 +339,7 @@ must provide the operation name as described in {GetOperation()}. ### Field Selections -Field Selections must exist on Object, Interface, Union and Tagged types. +Field selections must exist on Object, Interface, Union and Tagged types. Note: Tagged types define member fields which are queried with field selections. From ced63be93178f0c76baf93b5dbc05789f170d8f1 Mon Sep 17 00:00:00 2001 From: Benjie Gillam Date: Thu, 21 Jan 2021 18:11:52 +0000 Subject: [PATCH 43/43] Separate input and output tagged types --- spec/Appendix B -- Grammar Summary.md | 10 ++- spec/Section 3 -- Type System.md | 117 ++++++++++++++------------ spec/Section 4 -- Introspection.md | 28 +++++- spec/Section 5 -- Validation.md | 6 +- 4 files changed, 98 insertions(+), 63 deletions(-) diff --git a/spec/Appendix B -- Grammar Summary.md b/spec/Appendix B -- Grammar Summary.md index c041f8d31..1308c24ec 100644 --- a/spec/Appendix B -- Grammar Summary.md +++ b/spec/Appendix B -- Grammar Summary.md @@ -287,15 +287,19 @@ UnionTypeExtension : - extend union Name Directives[Const]? UnionMemberTypes - extend union Name Directives[Const] -TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedMemberFieldsDefinition? +TaggedTypeDefinition : Description? tagged TaggedTypeVariant Name Directives[Const]? TaggedMemberFieldsDefinition? + +TaggedTypeVariant : + - input + - output TaggedMemberFieldsDefinition : { TaggedMemberFieldDefinition+ } TaggedMemberFieldDefinition : Description? Name : Type Directives[Const]? TaggedTypeExtension : - - extend tagged Name Directives[Const]? TaggedMemberFieldsDefinition - - extend tagged Name Directives[Const] + - extend tagged TaggedTypeVariant Name Directives[Const]? TaggedMemberFieldsDefinition + - extend tagged TaggedTypeVariant Name Directives[Const] EnumTypeDefinition : Description? enum Name Directives[Const]? EnumValuesDefinition? diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index b3e9e0aca..65e61615a 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -303,9 +303,8 @@ like Scalar and Enum types, can be used as both input types and output types; other kinds of types can only be used in one or the other. Input Object types 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. Tagged types may be used -as input types or output types depending on how the types of their member -fields may be used. +types depending on how the wrapped type may be used. There are two variants of +tagged types - one for use as an input, and one for use as an output. IsInputType(type) : * If {type} is a List type or Non-Null type: @@ -313,9 +312,7 @@ IsInputType(type) : * Return IsInputType({unwrappedType}) * If {type} is a Scalar, Enum, or Input Object type: * Return {true} - * If {type} is a Tagged type: - * If there exists a member field in {type} where {IsInputType(memberFieldType)} is {false} - * Return {false} + * If {type} is an input Tagged type: * Return {true} * Return {false} @@ -325,9 +322,7 @@ IsOutputType(type) : * Return IsOutputType({unwrappedType}) * If {type} is a Scalar, Object, Interface, Union, or Enum type: * Return {true} - * If {type} is a Tagged type: - * If there exists a member field in {type} where {IsOutputType(memberFieldType)} is {false} - * Return {false} + * If {type} is an output Tagged type: * Return {true} * Return {false} @@ -933,7 +928,7 @@ May yield the result: The type of an object field argument must be an input type (any type for which {IsInputType(type)} returns {true}, which includes Object, Interface, Union and -some Tagged types). +input Tagged types). ### Field Deprecation @@ -1008,8 +1003,8 @@ the implementing type will define all fields defined by those interfaces. Fields on a GraphQL interface have the same rules as fields on a GraphQL object; their type can be any for which {IsOutputType(type)} returns {true}: -Scalar, Object, Enum, Interface, Union, some Tagged types, or any wrapping -types of those. +Scalar, Object, Enum, Interface, Union, output Tagged types, or any wrapping +types of those six. For example, an interface `NamedEntity` may describe a required field and types such as `Person` or `Business` may then implement this interface to guarantee @@ -1370,11 +1365,15 @@ Union type extensions have the potential to be invalid if incorrectly defined. ## Tagged Types -TaggedTypeDefinition : Description? tagged Name Directives[Const]? TaggedMemberFieldsDefinition? +TaggedTypeDefinition : Description? tagged TaggedTypeVariant Name Directives[Const]? TaggedMemberFieldsDefinition? + +TaggedTypeVariant : + - input + - output TaggedTypeExtension : - - extend tagged Name Directives[Const]? TaggedMemberFieldsDefinition - - extend tagged Name Directives[Const] + - extend tagged TaggedTypeVariant Name Directives[Const]? TaggedMemberFieldsDefinition + - extend tagged TaggedTypeVariant Name Directives[Const] TaggedMemberFieldsDefinition : { TaggedMemberFieldDefinition+ } @@ -1382,11 +1381,14 @@ TaggedMemberFieldDefinition : Description? Name : Type Directives[Const]? Tagged types represent a list of possible named member fields, exactly one of which must be present. This resulting member field must yield a value of a -specific type. In outputs, like for Objects, Tagged types should be serialized -as ordered maps: the queried member field names (or aliases) that are present -are the keys and the result of evaluating the member field is the value, -ordered by the order in which they appear in the query. Note that Tagged types -may be objects with more than one key even though they guarantee that exactly +specific type. Every tagged type must be suitable for input or for output, but +not both; this input/output suitability is referred to as the variant. + +Output Tagged types, like Objects, should be serialized as ordered maps: the +queried member field names (or aliases) that are present are the keys and the +result of evaluating the member field is the value, ordered by the order in +which they appear in the query. Note that the resulting map for a Tagged types +may be an object with more than one key even though they guarantee that exactly one member field is present, this is because aliases may be used, and `__typename` may also be queried (and will return the name of the tagged type). @@ -1397,7 +1399,7 @@ introspection system. For example, a type `PackQuantity` could be described as: ```graphql example -tagged PackQuantity { +tagged output PackQuantity { volumeInMetresCubed: Float! numberOfBoxes: Int! description: String! @@ -1410,13 +1412,12 @@ In this case we're representing exactly one of the following: - an object containing a single key `numberOfBoxes` with a {Int} value - an object containing a single key `description` with an {String} value -The `PackQuantity` Tagged type is valid as both an input and output type, but -this is not true of all tagged types. If a Tagged type has a member field whose -type is only valid for output, then the Tagged type is only valid for output. -If a Tagged type has a member field whose type is only valid for input, then -the Tagged type is only valid for input. If a Tagged type has a member field -that is only valid for input, and another member field that's only valid for -output, then it is not a valid Tagged type. +The `PackQuantity` Tagged type above was declared as an `output`, but it could +have been declared as an `instead` instead since it only references types that +are suitable for both input and output. This is not true of all tagged types. +Output tagged types may only contain member fields whose type is valid for +output. Input tagged types may only contain member fields whose type is valid +for input. A member field of a Tagged type may be of any valid GraphQL type, including Scalar, Enum, Input Object type, Object type, an Interface, a Union, another @@ -1473,8 +1474,8 @@ And may yield one of the following results: **Field Ordering** -Like with Object types, when querying a Tagged type, the resulting mapping of -fields are conceptually ordered in the same order in which they were +Like with Object types, when querying an output Tagged type, the resulting +mapping of fields are conceptually ordered in the same order in which they were encountered during query execution, excluding fragments for which the type does not apply and fields or fragments that are skipped via `@skip` or `@include` directives, and fields which are neither the single matched member field nor @@ -1483,16 +1484,14 @@ using the {CollectFields()} algorithm. **Result Coercion** -Determining the result of coercing an object or tagged type is the heart of the -GraphQL executor, so this is covered in that section of the spec. +Determining the result of coercing an object or output tagged type is the heart +of the GraphQL executor, so this is covered in that section of the spec. -Note: only Tagged types for which {IsOutputType(type)} returns {true} may be -used in output. +Note: only output Tagged types may be used in output. **Input Coercion** -Only Tagged types for which {IsInputType(type)} returns {true} may be used as -input. +Only input Tagged types may be used as input. The value for an input Tagged type should be an input object literal or an unordered map supplied by a variable, otherwise a query error must be thrown. @@ -1525,11 +1524,11 @@ map is constructed with the following rules: * No entry should be added to the unordered map for any other member fields defined on the Tagged type. -Following are examples of input coercion for a Tagged type with a `String` -member field `a` and a required (non-null) `Int!` member field `b`: +Following are examples of input coercion for an input Tagged type with a +`String` member field `a` and a required (non-null) `Int!` member field `b`: ```graphql example -tagged ExampleInputTagged { +tagged input ExampleInputTagged { a: String b: Int! } @@ -1561,12 +1560,17 @@ Tagged types have the potential to be invalid if incorrectly defined. This set of rules must be adhered to by every Tagged type in a GraphQL schema. 1. A Tagged type must define one or more member fields. -2. For each member field of a Tagged type: +2. A Tagged type must be either an input tagged type or an output tagged type + (but not both). +3. For each member field of a Tagged type: 1. The member field must have a unique name within that Tagged type; no two member fields may share the same name. 2. The member field must not have a name which begins with the characters {"__"} (two underscores). -3. {IsOutputType(taggedType)} and {IsInputType(taggedType)} cannot both be {false}. + 3. If the Tagged type is an input Tagged type, {IsInputType(memberType)} + must be {true}. + 4. If the Tagged type is an output Tagged type, {IsOutputType(memberType)} + must be {true}. ### Member Field Deprecation @@ -1580,8 +1584,8 @@ When using the type system definition language, `@deprecated` directives are used to indicate that a field is deprecated: ```graphql example -tagged ExampleType { - oldField: String @deprecated +tagged output ExampleType { + oldField: String! @deprecated } ``` @@ -1591,8 +1595,8 @@ a tagged type deprecated even when that member field is non-nullable. ### Tagged Extensions TaggedTypeExtension : - - extend tagged Name Directives[Const]? TaggedMemberFieldsDefinition - - extend tagged Name Directives[Const] + - extend tagged TaggedTypeVariant Name Directives[Const]? TaggedMemberFieldsDefinition + - extend tagged TaggedTypeVariant Name Directives[Const] Tagged type extensions are used to represent a Tagged type which has been extended from some original Tagged type. For example, this might be used to @@ -1600,10 +1604,10 @@ represent a GraphQL service which is itself an extension of another GraphQL service. In this example, an additional option `startsWith` is added to a `StringFilter` -type: +input Tagged type: ```graphql example -extend tagged StringFilter { +extend tagged input StringFilter { startsWith: String! } ``` @@ -1615,7 +1619,7 @@ In this example, a directive is added to a `StringFilter` type without adding member fields: ```graphql example -extend tagged StringFilter @addedDirective +extend tagged input StringFilter @addedDirective ``` **Type Validation** @@ -1623,16 +1627,19 @@ extend tagged StringFilter @addedDirective Tagged type extensions have the potential to be invalid if incorrectly defined. 1. The named type must already be defined and must be a Tagged type. -2. The member fields of a Tagged type extension must have unique names; no two +2. The variant of the extension must match the variant of the existing Tagged + type (they must both be input, or both be output). +3. The member fields of a Tagged type extension must have unique names; no two member fields may share the same name. -3. The member fields of a Tagged type extension must not have names beginning +4. The member fields of a Tagged type extension must not have names beginning with {"__"}. -4. Any member fields of a Tagged type extension must not be already defined on +5. Any member fields of a Tagged type extension must not be already defined on the original Tagged type. -5. Member fields of a Tagged type extension must not cause - {IsOutputType(taggedType)} and {IsInputType(taggedType)} to both become - {false}. -6. Any non-repeatable directives provided must not already apply to the +6. If the Tagged type is an input Tagged type then all member fields of a + Tagged type extension must have {IsInputType(memberType)} equal to {true}. +6. If the Tagged type is an output Tagged type then all member fields of a + Tagged type extension must have {IsOutputType(memberType)} equal to {true}. +7. Any non-repeatable directives provided must not already apply to the original Tagged type. diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 9d9e0f00c..1514cf543 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -90,9 +90,9 @@ warnings. ## Type Name Introspection GraphQL supports type name introspection at any point within a query by the -meta-field __typename: String! when querying against any Object, Interface, +meta-field `__typename: String!` when querying against any Object, Interface, Union or Tagged types. It returns the name of the concrete type currently being -queried, which will be an Object or Tagged type. +queried, which will be an Object or output Tagged type. This is most often used when querying against Interface or Union types to identify which actual type of the possible types has been returned. @@ -151,6 +151,12 @@ type __Type { # should be non-null for NON_NULL and LIST only, must be null for the others ofType: __Type + + # should return true for any type for which {IsInputType(type)} returns true. + isInputType: Boolean + + # should return true for any type for which {IsOutputType(type)} returns true. + isOutputType: Boolean } type __Field { @@ -257,6 +263,8 @@ Fields * `kind` must return `__TypeKind.SCALAR`. * `name` must return a String. * `description` may return a String or {null}. +* `isInputType` should return {true}. +* `isOutputType` should return {true}. * All other fields must return {null}. @@ -274,6 +282,8 @@ Fields * Accepts the argument `includeDeprecated` which defaults to {false}. If {true}, deprecated fields are also returned. * `interfaces`: The set of interfaces that an object implements. +* `isInputType` should return {false}. +* `isOutputType` should return {true}. * All other fields must return {null}. @@ -290,6 +300,8 @@ Fields * `description` may return a String or {null}. * `possibleTypes` returns the list of types that can be represented within this union. They must be object types. +* `isInputType` should return {false}. +* `isOutputType` should return {true}. * All other fields must return {null}. @@ -311,6 +323,8 @@ Fields * `interfaces`: The set of interfaces that this interface implements. * `possibleTypes` returns the list of types that implement this interface. They must be object types. +* `isInputType` should return {false}. +* `isOutputType` should return {true}. * All other fields must return {null}. @@ -330,6 +344,8 @@ Fields * `memberFields`: The set of member fields query-able on this type. * Accepts the argument `includeDeprecated` which defaults to {false}. If {true}, deprecated member fields are also returned. +* `isInputType` should return {IsInputType(type)}. +* `isOutputType` should return {IsOutputType(type)}. * All other fields must return {null}. @@ -346,6 +362,8 @@ Fields must have unique names. * Accepts the argument `includeDeprecated` which defaults to {false}. If {true}, deprecated enum values are also returned. +* `isInputType` should return {true}. +* `isOutputType` should return {true}. * All other fields must return {null}. @@ -369,6 +387,8 @@ Fields * `name` must return a String. * `description` may return a String or {null}. * `inputFields`: a list of `InputValue`. +* `isInputType` should return {true}. +* `isOutputType` should return {false}. * All other fields must return {null}. @@ -382,6 +402,8 @@ Fields * `kind` must return `__TypeKind.LIST`. * `ofType`: Any type. +* `isInputType` should return {IsInputType(type)}. +* `isOutputType` should return {IsOutputType(type)}. * All other fields must return {null}. @@ -395,6 +417,8 @@ required inputs for arguments and input object fields. * `kind` must return `__TypeKind.NON_NULL`. * `ofType`: Any type except Non-null. +* `isInputType` should return {IsInputType(type)}. +* `isOutputType` should return {IsOutputType(type)}. * All other fields must return {null}. diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index 9c0601161..56600bd76 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -82,14 +82,14 @@ union CatOrDog = Cat | Dog union DogOrHuman = Dog | Human union HumanOrAlien = Human | Alien -tagged Being { +tagged output Being { cat: Cat dog: Dog human: Human alien: Alien } -tagged Earthling { +tagged output Earthling { cat: Cat dog: Dog human: Human @@ -585,7 +585,7 @@ fragment conflictingDifferingResponses on Pet { * Let {selectionType} be the result type of {selection} * If {selectionType} is a scalar or enum: * The subselection set of that selection must be empty -* If {selectionType} is an interface, union, object or tagged type +* If {selectionType} is an interface, union, object or output tagged type * The subselection set of that selection must NOT BE empty **Explanatory Text**