Skip to content

RFC: inputUnion type #395

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 104 additions & 3 deletions spec/Section 3 -- Type System.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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**
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should say:

the concrete Input Object type

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
Expand Down
18 changes: 17 additions & 1 deletion spec/Section 4 -- Introspection.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ type __Type {
# OBJECT only
interfaces: [__Type!]

# INTERFACE and UNION only
# INTERFACE, UNION, and INPUT_UNION only
possibleTypes: [__Type!]

# ENUM only
Expand Down Expand Up @@ -182,6 +182,7 @@ enum __TypeKind {
UNION
ENUM
INPUT_OBJECT
INPUT_UNION
LIST
NON_NULL
}
Expand Down Expand Up @@ -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

Expand Down
17 changes: 16 additions & 1 deletion spec/Section 5 -- Validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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
}
```
Expand All @@ -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:
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions spec/Section 6 -- Execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down