The addition of an Input Union type has been discussed in the GraphQL community for many years now. The value of this feature has largely been agreed upon, but the implementation has not.
This document attempts to bring together all the various solutions that have been discussed with the goal of reaching a shared understanding of the problem space.
From that shared understanding, this document can then evolve into a proposed solution.
Categories:
- Value-based discriminator field
- Structural discrimination
These options rely the value of a specific input field to express the concrete type.
This solution was discussed in graphql#395
input AddPostInput {
title: String!
body: String!
}
input AddImageInput {
title: String!
photo: String!
caption: String
}
inputUnion AddMediaBlockInput = AddPostInput | AddImageInput
type Mutation {
addContent(content: AddMediaBlockInput!): Content
}
# Variables:
{
content: {
"__typename": "AddPostInput",
title: "Title",
body: "body..."
}
}- A
defaultannotation may be provided, for which specifying the__typenameis not required. This enables a field migration from anInputto anInput Union
input AddPostInput {
kind: <AddMediaBlockInput>
title: String!
body: String!
}
input AddImageInput {
kind: <AddMediaBlockInput>
title: String!
photo: String!
caption: String
}
inputUnion AddMediaBlockInput = AddPostInput | AddImageInput
type Mutation {
addContent(content: AddMediaBlockInput!): Content
}
# Variables:
{
content: {
kind: "AddPostInput",
title: "Title",
body: "body..."
}
}- The discriminator field is non-sensical if the input is used outside of an input union.
This solution is derrived from one discussed in graphql#488
enum MediaType {
POST
IMAGE
}
input AddPostInput {
kind: MediaType::POST
title: String!
body: String!
}
input AddImageInput {
kind: MediaType::IMAGE
title: String!
photo: String!
caption: String
}
inputUnion AddMediaBlockInput = AddPostInput | AddImageInput
type Mutation {
addContent(content: AddMediaBlockInput!): Content
}
# Variables:
{
content: {
kind: "POST",
title: "Title",
body: "body..."
}
}- Literal strings used instead of an
enum
input AddPostInput {
kind: 'post'
title: String!
body: String!
}
input AddImageInput {
kind: 'image'
title: String!
photo: String!
caption: String
}- The discriminator field is redundant if the input is used outside of an input union.
These options rely on the structure of the input to determine the concrete type.
The concrete type is the first type in the input union definition that matches.
input AddPostInput {
title: String!
publishedAt: Int
body: String
}
input AddImageInput {
title: String!
publishedAt: Int
photo: String
caption: String
}
inputUnion AddMediaBlockInput = AddPostInput | AddImageInput
type Mutation {
addContent(content: AddMediaBlockInput!): Content
}
# Variables:
{
content: {
title: "Title",
date: 1558066429
# AddPostInput
}
}
{
content: {
title: "Title",
date: 1558066429
photo: "photo.png"
# AddImageInput
}
}Schema Rule: Each type in the union must have a unique set of required field names
input AddPostInput {
title: String!
body: String!
}
input AddImageInput {
photo: String!
caption: String
}
inputUnion AddMediaBlockInput = AddPostInput | AddImageInput
type Mutation {
addContent(content: AddMediaBlockInput!): Content
}
# Variables:
{
content: {
title: "Title",
body: "body..."
# AddPostInput
}
}An invalid schema:
input AddPostInput {
title: String!
body: String!
}
input AddDatedPostInput {
title: String!
body: String!
date: Int
}
input AddImageInput {
photo: String!
caption: String
}
inputUnion AddMediaBlockInput = AddPostInput | AddDatedPostInput | AddImageInput
type Mutation {
addContent(content: AddMediaBlockInput!): Content
}- Optional fields could prevent determining a unique type
input AddPostInput {
title: String!
body: String!
date: Int
}
input AddDatedPostInput {
title: String!
body: String!
date: Int!
}Workaround? : Each type's set of required fields must be uniquely identifying
- A type's set of required field names must not match the set of another type's required field names
- A type's set of required field names must not overlap with the set of another type's required or optional field names
Workaround? : Each type must have at least one unique required field
- A type must contain one required field that is not a field in any other type
- Consider the field type along with the field name when determining uniqueness.
This solution was presented in graphql#395 (comment)
The type is determined by using an intermediate input type that maps field name to type.
A directive has also been discussed to specify that only one of the fields may be selected. See graphql#586.
input AddPostInput {
title: String!
body: String!
}
input AddImageInput {
photo: String!
caption: String
}
input AddMediaBlockInput @oneOf {
post: AddPostInput
image: AddImageInput
}
type Mutation {
addContent(content: AddMediaBlockInput!): Content
}
# Variables:
{
content: {
post: {
title: "Title",
body: "body..."
}
}
}