diff --git a/spec/Section 3 -- Type System.md b/spec/Section 3 -- Type System.md index 7f93c9bcc..8e517807b 100644 --- a/spec/Section 3 -- Type System.md +++ b/spec/Section 3 -- Type System.md @@ -29,7 +29,7 @@ introspection system. ## Types -The fundamental unit of any GraphQL Schema is the type. There are eight kinds +The fundamental unit of any GraphQL Schema is the type. There are nine kinds of types in GraphQL. The most basic type is a `Scalar`. A scalar represents a primitive value, like @@ -41,7 +41,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 inputUnions. An `Interface` defines a list of fields; `Object` types that implement that interface are guaranteed to implement those fields. Whenever the type system @@ -51,6 +51,9 @@ 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. +An `Input Union` defines a list of possible `Input Object` types; `Input Union` +types are fulfilled by a single member `Input Object`. + All of the types so far are assumed to be both nullable and singular: e.g. a scalar string returns either null or a singular string. The type system might want to define that it returns a list of other types; the `List` type is provided for @@ -795,7 +798,7 @@ input argument. For this reason, input objects have a separate type in the system. An `Input Object` defines a set of input fields; the input fields are either -scalars, enums, or other input objects. This allows arguments to accept +scalars, enums, input unions, or other input objects. This allows arguments to accept arbitrarily complex structs. **Result Coercion** @@ -862,6 +865,104 @@ Literal Value | Variables | Coerced Value 3. The return types of each defined field must be an Input type. +### Input Unions + +GraphQL Input Unions represent an argument that could be one of a list of GraphQL +Input Object types, determined at execution. + +The `Union` type defined above is inappropriate for re-use here, because a `Union` +contains `Object` types. `Object` types are not permitted as inputs, and `Input Object` +types are not permitted in query results so these types should be grouped separately. + +`Input Union` types may also be used as values for input objects, allowing for +arbitrarily nested structs. In order to determine the concrete `Object` type fulfilling +the Input Union, the `__inputname` field must be specified as a member of the input object. +This field value must be a string representing a type name of the `Input Object` which is +both a member of the `Input Union` and is fulfilled by the fields of the input. + +For example, we might have the following type system: + +```graphql example +inputUnion ContentBlock = ParagraphInput | PhotoInput + +input ParagraphInput { + text: String +} + +type PhotoInput { + image: String! + title: String! +} + +type Mutation { + createPost(content: [ContentBlock]!): Post +} +``` + +When executing the `createPost` mutation, the mutation accepts the input union +`ContentBlock`, and the values provided must both be a member of the input union +and specify which input object type it is fulfilling. + +Examples of valid input: + +```graphql-example +[ + {__inputname: "ParagraphInput", text: "This is a block of text" }, + {__inputname: "PhotoInput", image: "http://example.com/image.png", title: "Example Image"}, + {__inputname: "ParagraphInput", text: "This is a block of after the photo" }, +] +``` + +```graphql-example +{__inputname: "ParagraphInput", text: "This is a block of text with a single member" } +``` + +And invalid inputs: + +```graphql counter-example +[ + {text: "invalid:, __inputname is not specified"} +] +``` + +```graphql counter-example +[ + { + __inputname: "PhotoInput", + text: "invalid: text is not a member of input", + image: "http://example.com/error.png", + title: "Error Image" + } +] +``` + +**Result Coercion** + +An input object is never a valid result. + +**Input Coercion** + +The value for an input union should be an input object literal or an unordered +map supplied by a variable, otherwise an error should be thrown. The input +object literal must contain a property `__inputname` with a string value +of the name of the input object member of the input union, otherwise an error +should be throw. All other fields must be valid in fulfilling the input object +represented by the `__inputname` as defined in Input Object coercion. + +The result of coercion is that of a standard input object coercion, with an +additional field `__inputname` containing the name of the type fulfilling the +input union. + +#### Input Union type validation + +Input Union types have the potential to be invalid if incorrectly defined. + +1. The member types of an Input Union type must all be Input Object base types; + Scalar, Interface and Input Union types may not be member types of an + Input Union. Similarly, wrapping types may not be member types of a Union. +2. An Input Union type must define one or more unique member types. + + ### Lists A GraphQL list is a special collection type which declares the type of each diff --git a/spec/Section 4 -- Introspection.md b/spec/Section 4 -- Introspection.md index 837ed3c8b..935290890 100644 --- a/spec/Section 4 -- Introspection.md +++ b/spec/Section 4 -- Introspection.md @@ -139,7 +139,7 @@ type __Type { # OBJECT only interfaces: [__Type!] - # INTERFACE and UNION only + # INTERFACE, UNION, and INPUT_UNION only possibleTypes: [__Type!] # ENUM only @@ -182,6 +182,7 @@ enum __TypeKind { UNION ENUM INPUT_OBJECT + INPUT_UNION LIST NON_NULL } @@ -268,6 +269,21 @@ Fields union. They must be object types. * All other fields must return {null}. +#### Input Union + +Input Unions are an abstract type containing possible a list of possible valid +input values. The possible inputs of an input union are explicitly listed out +in `possibleTypes`. Input types can be made parts of input unions without +modification of that type. + +Fields + +* `kind` must return `__TypeKind.INPUT_UNION`. +* `name` must return a String. +* `description` may return a String or {null}. +* `possibleTypes` returns the list of types that can be represented within this + union. They must be input object types. +* All other fields must return {null}. #### Interface diff --git a/spec/Section 5 -- Validation.md b/spec/Section 5 -- Validation.md index f61c0ddf1..dc9c1f456 100644 --- a/spec/Section 5 -- Validation.md +++ b/spec/Section 5 -- Validation.md @@ -1507,7 +1507,7 @@ query houseTrainedQuery($atOtherHomes: Boolean! = true) { **Explanatory Text** -Variables can only be scalars, enums, input objects, or lists and non-null +Variables can only be scalars, enums, input objects, input unions, or lists and non-null variants of those types. These are known as input types. Objects, unions, and interfaces cannot be used as inputs. @@ -1516,8 +1516,13 @@ For these examples, consider the following typesystem additions: ```graphql example input ComplexInput { name: String, owner: String } +input AlternateComplexInput { breed: String } + +inputUnion ComplexInputUnion = ComplexInput | AlternateComplexInput + extend type QueryRoot { findDog(complex: ComplexInput): Dog + findDogs(complexUnion: ComplexInputUnion): [Dog] booleanList(booleanListArg: [Boolean!]): Boolean } ``` @@ -1540,6 +1545,12 @@ query takesComplexInput($complexInput: ComplexInput) { query TakesListOfBooleanBang($booleans: [Boolean!]) { booleanList(booleanListArg: $booleans) } + +query fulfillsComplexInputUnion($complexUnion: ComplexInputUnion) { + findDogs(complexUnion: $complexUnion) { + name + } +} ``` The following queries are invalid: @@ -1806,6 +1817,10 @@ an extraneous variable. * AreTypesCompatible({argumentType}, {variableType}, {hasDefault}): * If {hasDefault} is true, treat the {variableType} as non-null. * If inner type of {argumentType} and {variableType} are different, return false + * If {variableType} is an input union + * Let {inputName} be the value of the `__inputname` property of {argumentType} + * Let {possibleTypes} be a set of possible types of {variableType} + * If {inputName} is not a member of {possibleTypes}, return false * If {argumentType} and {variableType} have different list dimensions, return false * If any list level of {variableType} is not non-null, and the corresponding level in {argument} is non-null, the types are not compatible. diff --git a/spec/Section 6 -- Execution.md b/spec/Section 6 -- Execution.md index bdfcf4193..b87d3462c 100644 --- a/spec/Section 6 -- Execution.md +++ b/spec/Section 6 -- Execution.md @@ -528,8 +528,8 @@ ExecuteField(objectType, objectValue, fieldType, fields, variableValues): Fields may include arguments which are provided to the underlying runtime in order to correctly produce a value. These arguments are defined by the field in -the type system to have a specific input type: Scalars, Enum, Input Object, or -List or Non-Null wrapped variations of these three. +the type system to have a specific input type: Scalars, Enum, Input Object, Input Union, or +List or Non-Null wrapped variations of these four. At each argument position in a query may be a literal value or a variable to be provided at runtime.