Skip to content

Commit f9cf139

Browse files
binaryseedIvanGoncharov
authored andcommitted
Start Input Union RFC document (#584)
* Start Input Union RFC document * adopt common example types * add order based solution; clean-up formatting * add tagged union
1 parent 972f2cb commit f9cf139

File tree

1 file changed

+314
-0
lines changed

1 file changed

+314
-0
lines changed

rfcs/InputUnion.md

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
RFC: GraphQL Input Union
2+
----------
3+
4+
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.
5+
6+
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.
7+
8+
From that shared understanding, this document can then evolve into a proposed solution.
9+
10+
## Possible Solutions
11+
12+
Categories:
13+
14+
* Value-based discriminator field
15+
* Structural discrimination
16+
17+
### Value-based discriminator field
18+
19+
These options rely the **value** of a specific input field to express the concrete type.
20+
21+
#### Single `__typename` field; value is the `type`
22+
23+
This solution was discussed in https://github.com/graphql/graphql-spec/pull/395
24+
25+
```graphql
26+
input AddPostInput {
27+
title: String!
28+
body: String!
29+
}
30+
input AddImageInput {
31+
title: String!
32+
photo: String!
33+
caption: String
34+
}
35+
36+
inputUnion AddMediaBlockInput = AddPostInput | AddImageInput
37+
38+
type Mutation {
39+
addContent(content: AddMediaBlockInput!): Content
40+
}
41+
42+
# Variables:
43+
{
44+
content: {
45+
"__typename": "AddPostInput",
46+
title: "Title",
47+
body: "body..."
48+
}
49+
}
50+
```
51+
52+
##### Variations:
53+
54+
* A `default` annotation may be provided, for which specifying the `__typename` is not required. This enables a field migration from an `Input` to an `Input Union`
55+
56+
#### Single user-chosen field; value is the `type`
57+
58+
```graphql
59+
input AddPostInput {
60+
kind: <AddMediaBlockInput>
61+
title: String!
62+
body: String!
63+
}
64+
input AddImageInput {
65+
kind: <AddMediaBlockInput>
66+
title: String!
67+
photo: String!
68+
caption: String
69+
}
70+
71+
inputUnion AddMediaBlockInput = AddPostInput | AddImageInput
72+
73+
type Mutation {
74+
addContent(content: AddMediaBlockInput!): Content
75+
}
76+
77+
# Variables:
78+
{
79+
content: {
80+
kind: "AddPostInput",
81+
title: "Title",
82+
body: "body..."
83+
}
84+
}
85+
```
86+
87+
##### Problems:
88+
89+
* The discriminator field is non-sensical if the input is used _outside_ of an input union.
90+
91+
#### Single user-chosen field; value is a literal
92+
93+
This solution is derrived from one discussed in https://github.com/graphql/graphql-spec/issues/488
94+
95+
```graphql
96+
enum MediaType {
97+
POST
98+
IMAGE
99+
}
100+
input AddPostInput {
101+
kind: MediaType::POST
102+
title: String!
103+
body: String!
104+
}
105+
input AddImageInput {
106+
kind: MediaType::IMAGE
107+
title: String!
108+
photo: String!
109+
caption: String
110+
}
111+
112+
inputUnion AddMediaBlockInput = AddPostInput | AddImageInput
113+
114+
type Mutation {
115+
addContent(content: AddMediaBlockInput!): Content
116+
}
117+
118+
# Variables:
119+
{
120+
content: {
121+
kind: "POST",
122+
title: "Title",
123+
body: "body..."
124+
}
125+
}
126+
```
127+
128+
##### Variations:
129+
130+
* Literal strings used instead of an `enum`
131+
132+
```graphql
133+
input AddPostInput {
134+
kind: 'post'
135+
title: String!
136+
body: String!
137+
}
138+
input AddImageInput {
139+
kind: 'image'
140+
title: String!
141+
photo: String!
142+
caption: String
143+
}
144+
```
145+
146+
##### Problems:
147+
148+
* The discriminator field is redundant if the input is used _outside_ of an input union.
149+
150+
### Structural discrimination
151+
152+
These options rely on the **structure** of the input to determine the concrete type.
153+
154+
#### Order based type matching
155+
156+
The concrete type is the first type in the input union definition that matches.
157+
158+
```graphql
159+
input AddPostInput {
160+
title: String!
161+
publishedAt: Int
162+
body: String
163+
}
164+
input AddImageInput {
165+
title: String!
166+
publishedAt: Int
167+
photo: String
168+
caption: String
169+
}
170+
171+
inputUnion AddMediaBlockInput = AddPostInput | AddImageInput
172+
173+
type Mutation {
174+
addContent(content: AddMediaBlockInput!): Content
175+
}
176+
177+
# Variables:
178+
{
179+
content: {
180+
title: "Title",
181+
date: 1558066429
182+
# AddPostInput
183+
}
184+
}
185+
{
186+
content: {
187+
title: "Title",
188+
date: 1558066429
189+
photo: "photo.png"
190+
# AddImageInput
191+
}
192+
}
193+
```
194+
195+
#### Structural uniqueness
196+
197+
Schema Rule: Each type in the union must have a unique set of required field names
198+
199+
```graphql
200+
input AddPostInput {
201+
title: String!
202+
body: String!
203+
}
204+
input AddImageInput {
205+
photo: String!
206+
caption: String
207+
}
208+
209+
inputUnion AddMediaBlockInput = AddPostInput | AddImageInput
210+
211+
type Mutation {
212+
addContent(content: AddMediaBlockInput!): Content
213+
}
214+
215+
# Variables:
216+
{
217+
content: {
218+
title: "Title",
219+
body: "body..."
220+
# AddPostInput
221+
}
222+
}
223+
```
224+
225+
An invalid schema:
226+
227+
```graphql
228+
input AddPostInput {
229+
title: String!
230+
body: String!
231+
}
232+
input AddDatedPostInput {
233+
title: String!
234+
body: String!
235+
date: Int
236+
}
237+
input AddImageInput {
238+
photo: String!
239+
caption: String
240+
}
241+
242+
inputUnion AddMediaBlockInput = AddPostInput | AddDatedPostInput | AddImageInput
243+
244+
type Mutation {
245+
addContent(content: AddMediaBlockInput!): Content
246+
}
247+
```
248+
249+
##### Problems:
250+
251+
* Optional fields could prevent determining a unique type
252+
253+
```graphql
254+
input AddPostInput {
255+
title: String!
256+
body: String!
257+
date: Int
258+
}
259+
input AddDatedPostInput {
260+
title: String!
261+
body: String!
262+
date: Int!
263+
}
264+
```
265+
266+
Workaround? : Each type's set of required fields must be uniquely identifying
267+
268+
- A type's set of required field names must not match the set of another type's required field names
269+
- A type's set of required field names must not overlap with the set of another type's required or optional field names
270+
271+
Workaround? : Each type must have at least one unique required field
272+
273+
- A type must contain one required field that is not a field in any other type
274+
275+
##### Variations:
276+
277+
* Consider the field _type_ along with the field _name_ when determining uniqueness.
278+
279+
#### One Of (Tagged Union)
280+
281+
This solution was presented in https://github.com/graphql/graphql-spec/pull/395#issuecomment-361373097
282+
283+
The type is determined by using an intermediate input type that maps field name to type.
284+
285+
A directive has also been discussed to specify that only one of the fields may be selected. See https://github.com/graphql/graphql-spec/pull/586.
286+
287+
```graphql
288+
input AddPostInput {
289+
title: String!
290+
body: String!
291+
}
292+
input AddImageInput {
293+
photo: String!
294+
caption: String
295+
}
296+
input AddMediaBlockInput @oneOf {
297+
post: AddPostInput
298+
image: AddImageInput
299+
}
300+
301+
type Mutation {
302+
addContent(content: AddMediaBlockInput!): Content
303+
}
304+
305+
# Variables:
306+
{
307+
content: {
308+
post: {
309+
title: "Title",
310+
body: "body..."
311+
}
312+
}
313+
}
314+
```

0 commit comments

Comments
 (0)