Skip to content

Commit 10331b0

Browse files
benjiemichaelstaibShane32yaacovCRglen-84
authored
RFC: OneOf Input Objects (#825)
* Renumber list items * @OneOf input objects * @OneOf fields * Fix typos (thanks @eapache!) * Much stricter validation for oneof literals (with examples) * Add missing coercion rule * Clearer wording of oneof coercion rule * Add more examples for clarity * Rename introspection fields to oneOf * Oneof's now require exactly one field/argument, and non-nullable variables. * Remove extraneous newline * graphgl -> graphql * Apply suggestions from @eapache's review * Apply suggestions from code review Co-authored-by: Michael Staib <[email protected]> * Update spec/Section 3 -- Type System.md * Remove Oneof Fields from spec * Oneof -> OneOf * Spellings * Remove out of date example * Rename __Type.oneOf to __Type.isOneOf * Add a:null example * Rewrite to avoid ambiguity of language * Forbid 'extend input' from introducing the @OneOf directive * Add yet more examples to the example coercion table * Indicate `@oneOf` is a built-in directive Co-authored-by: Shane Krueger <[email protected]> * Update spec/Section 3 -- Type System.md * remove OneOf-specific rule in favor of update to VariablesInAllowedPositions for simplicity, this PR retains the same problems for variables with defaults that are fixed by strict All Variable Usages Are Allowed * Clarify IsNonNullPosition algorithm * Clarify OneOf examples * Add more examples * Null literal is separate * Use 'execution error' and 'raise' rather than throw an error * Update spec/Section 3 -- Type System.md * Whitespace Co-authored-by: Glen <[email protected]> * Clarify algorithm * Rename 'Tagged' * editorial: define and link _OneOf Input Object_ * execution error -> request error (input coercion) * Simplify and clarify OneOf Input Object additional coercion rules * Clarity and correctness * Simplify * Use a colon * Use the correct error for the situation * Remove example which will not always fail until #1059 is adopted * copy tweaks and remove redundant examples * dedicated subsection * rogue plural + links * sp * sp --------- Co-authored-by: Michael Staib <[email protected]> Co-authored-by: Shane Krueger <[email protected]> Co-authored-by: Yaacov Rydzinski <[email protected]> Co-authored-by: Glen <[email protected]> Co-authored-by: Lee Byron <[email protected]>
1 parent cd0b8bd commit 10331b0

File tree

4 files changed

+183
-3
lines changed

4 files changed

+183
-3
lines changed

cspell.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ words:
2121
- tatooine
2222
- zuck
2323
- zuckerberg
24+
- brontie
25+
- oneOf
2426
# Forbid Alternative spellings
2527
flagWords:
2628
- implementor

spec/Section 3 -- Type System.md

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1568,7 +1568,7 @@ InputFieldsDefinition : { InputValueDefinition+ }
15681568
Fields may accept arguments to configure their behavior. These inputs are often
15691569
scalars or enums, but they sometimes need to represent more complex values.
15701570

1571-
A GraphQL Input Object defines a set of input fields; the input fields are
1571+
:: A GraphQL _Input Object_ defines a set of input fields; the input fields are
15721572
scalars, enums, other input objects, or any wrapping type whose underlying base
15731573
type is one of those three. This allows arguments to accept arbitrarily complex
15741574
structs.
@@ -1723,6 +1723,9 @@ input ExampleInputObject {
17231723
returns {true}.
17241724
4. If input field type is Non-Null and a default value is not defined:
17251725
1. The `@deprecated` directive must not be applied to this input field.
1726+
5. If the Input Object is a _OneOf Input Object_ then:
1727+
1. The type of the input field must be nullable.
1728+
2. The input field must not have a default value.
17261729
3. If an Input Object references itself either directly or through referenced
17271730
Input Objects, at least one of the fields in the chain of references must be
17281731
either a nullable or a List type.
@@ -1765,6 +1768,72 @@ InputFieldDefaultValueHasCycle(field, defaultValue, visitedFields):
17651768
- Return {InputObjectDefaultValueHasCycle(namedFieldType, fieldDefaultValue,
17661769
nextVisitedFields)}.
17671770

1771+
### OneOf Input Objects
1772+
1773+
:: A _OneOf Input Object_ is a special variant of _Input Object_ where exactly
1774+
one field must be set and non-null, all others being omitted. This is useful for
1775+
representing situations where an input may be one of many different options.
1776+
1777+
When using the type system definition language, the [`@oneOf`](#sec--oneOf)
1778+
directive is used to indicate that an Input Object is a OneOf Input Object (and
1779+
thus requires exactly one of its fields be provided):
1780+
1781+
```graphql
1782+
input UserUniqueCondition @oneOf {
1783+
id: ID
1784+
username: String
1785+
organizationAndEmail: OrganizationAndEmailInput
1786+
}
1787+
```
1788+
1789+
In schema introspection, the `__Type.isOneOf` field will return {true} for OneOf
1790+
Input Objects, and {false} for all other Input Objects.
1791+
1792+
**Input Coercion**
1793+
1794+
The value of a OneOf Input Object, as a variant of Input Object, must also be an
1795+
input object literal or an unordered map supplied by a variable, otherwise a
1796+
_request error_ must be raised.
1797+
1798+
- Prior to construction of the coerced map via the input coercion rules of an
1799+
_Input Object_: the value to be coerced must contain exactly one entry and
1800+
that entry must not be {null} or the {null} literal, otherwise a _request
1801+
error_ must be raised.
1802+
1803+
- All _Input Object_
1804+
[input coercion rules](http://localhost:3000/draft#sec-Input-Objects.Input-Coercion)
1805+
must also apply to an _OneOf Input Object_.
1806+
1807+
- The resulting coerced map must contain exactly one entry and the value for
1808+
that entry must not be {null}, otherwise an _execution error_ must be raised.
1809+
1810+
Following are additional examples of input coercion for a OneOf Input Object
1811+
type with a `String` member field `a` and an `Int` member field `b`:
1812+
1813+
```graphql example
1814+
input ExampleOneOfInputObject @oneOf {
1815+
a: String
1816+
b: Int
1817+
}
1818+
```
1819+
1820+
| Literal Value | Variables | Coerced Value |
1821+
| ----------------------- | ------------------------------- | --------------------------------------------------- |
1822+
| `{ a: "abc" }` | `{}` | `{ a: "abc" }` |
1823+
| `{ b: 123 }` | `{}` | `{ b: 123 }` |
1824+
| `$var` | `{ var: { a: "abc" } }` | `{ a: "abc" }` |
1825+
| `{ a: null }` | `{}` | Error: Value for member field {a} must be non-null |
1826+
| `$var` | `{ var: { a: null } }` | Error: Value for member field {a} must be non-null |
1827+
| `{ a: $a }` | `{}` | Error: Value for member field {a} must be specified |
1828+
| `{ a: "abc", b: 123 }` | `{}` | Error: Exactly one key must be specified |
1829+
| `{ a: 456, b: "xyz" }` | `{}` | Error: Exactly one key must be specified |
1830+
| `$var` | `{ var: { a: "abc", b: 123 } }` | Error: Exactly one key must be specified |
1831+
| `{ a: "abc", b: null }` | `{}` | Error: Exactly one key must be specified |
1832+
| `{ a: "abc", b: $b }` | `{}` | Error: Exactly one key must be specified |
1833+
| `{ a: $a, b: $b }` | `{ a: "abc" }` | Error: Exactly one key must be specified |
1834+
| `{}` | `{}` | Error: Exactly one key must be specified |
1835+
| `$var` | `{ var: {} }` | Error: Exactly one key must be specified |
1836+
17681837
### Input Object Extensions
17691838

17701839
InputObjectTypeExtension :
@@ -1788,6 +1857,12 @@ defined.
17881857
the previous Input Object.
17891858
4. Any non-repeatable directives provided must not already apply to the previous
17901859
Input Object type.
1860+
5. The `@oneOf` directive must not be provided by an Input Object type
1861+
extension.
1862+
6. If the original Input Object is a _OneOf Input Object_ then:
1863+
1. All fields of the Input Object type extension must be nullable.
1864+
2. All fields of the Input Object type extension must not have default
1865+
values.
17911866

17921867
## List
17931868

@@ -2013,6 +2088,9 @@ schema.
20132088
GraphQL implementations that support the type system definition language should
20142089
provide the `@specifiedBy` directive if representing custom scalar definitions.
20152090

2091+
GraphQL implementations that support the type system definition language should
2092+
provide the `@oneOf` directive if representing OneOf Input Objects.
2093+
20162094
When representing a GraphQL schema using the type system definition language any
20172095
_built-in directive_ may be omitted for brevity.
20182096

@@ -2227,3 +2305,20 @@ to the relevant IETF specification.
22272305
```graphql example
22282306
scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122")
22292307
```
2308+
2309+
### @oneOf
2310+
2311+
```graphql
2312+
directive @oneOf on INPUT_OBJECT
2313+
```
2314+
2315+
The `@oneOf` _built-in directive_ is used within the type system definition
2316+
language to indicate an _Input Object_ is a _OneOf Input Object_.
2317+
2318+
```graphql example
2319+
input UserUniqueCondition @oneOf {
2320+
id: ID
2321+
username: String
2322+
organizationAndEmail: OrganizationAndEmailInput
2323+
}
2324+
```

spec/Section 4 -- Introspection.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ type __Type {
151151
inputFields(includeDeprecated: Boolean! = false): [__InputValue!]
152152
# must be non-null for NON_NULL and LIST, otherwise null.
153153
ofType: __Type
154+
# must be non-null for INPUT_OBJECT, otherwise null.
155+
isOneOf: Boolean
154156
}
155157

156158
enum __TypeKind {
@@ -373,6 +375,8 @@ Fields\:
373375
- `inputFields` must return the set of input fields as a list of `__InputValue`.
374376
- Accepts the argument `includeDeprecated` which defaults to {false}. If
375377
{true}, deprecated input fields are also returned.
378+
- `isOneOf` must return {true} when representing a _OneOf Input Object_,
379+
otherwise {false}.
376380
- All other fields must return {null}.
377381

378382
**List**

spec/Section 5 -- Validation.md

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ type Query {
4040
findDog(searchBy: FindDogInput): Dog
4141
}
4242

43+
type Mutation {
44+
addPet(pet: PetInput!): Pet
45+
addPets(pets: [PetInput!]!): [Pet]
46+
}
47+
4348
enum DogCommand {
4449
SIT
4550
DOWN
@@ -92,6 +97,23 @@ input FindDogInput {
9297
name: String
9398
owner: String
9499
}
100+
101+
input CatInput {
102+
name: String!
103+
nickname: String
104+
meowVolume: Int
105+
}
106+
107+
input DogInput {
108+
name: String!
109+
nickname: String
110+
barkVolume: Int
111+
}
112+
113+
input PetInput @oneOf {
114+
cat: CatInput
115+
dog: DogInput
116+
}
95117
```
96118

97119
## Documents
@@ -1462,6 +1484,12 @@ query goodComplexDefaultValue($search: FindDogInput = { name: "Fido" }) {
14621484
name
14631485
}
14641486
}
1487+
1488+
mutation addPet($pet: PetInput! = { cat: { name: "Brontie" } }) {
1489+
addPet(pet: $pet) {
1490+
name
1491+
}
1492+
}
14651493
```
14661494

14671495
Non-coercible values (such as a String into an Int) are invalid. The following
@@ -1477,6 +1505,24 @@ query badComplexValue {
14771505
name
14781506
}
14791507
}
1508+
1509+
mutation oneOfWithNoFields {
1510+
addPet(pet: {}) {
1511+
name
1512+
}
1513+
}
1514+
1515+
mutation oneOfWithTwoFields($dog: DogInput) {
1516+
addPet(pet: { cat: { name: "Brontie" }, dog: $dog }) {
1517+
name
1518+
}
1519+
}
1520+
1521+
mutation listOfOneOfWithNullableVariable($dog: DogInput) {
1522+
addPets(pets: [{ dog: $dog }]) {
1523+
name
1524+
}
1525+
}
14801526
```
14811527

14821528
### Input Object Field Names
@@ -2003,8 +2049,8 @@ IsVariableUsageAllowed(variableDefinition, variableUsage):
20032049
- Let {variableType} be the expected type of {variableDefinition}.
20042050
- Let {locationType} be the expected type of the {Argument}, {ObjectField}, or
20052051
{ListValue} entry where {variableUsage} is located.
2006-
- If {locationType} is a non-null type AND {variableType} is NOT a non-null
2007-
type:
2052+
- If {IsNonNullPosition(locationType, variableUsage)} AND {variableType} is NOT
2053+
a non-null type:
20082054
- Let {hasNonNullVariableDefaultValue} be {true} if a default value exists for
20092055
{variableDefinition} and is not the value {null}.
20102056
- Let {hasLocationDefaultValue} be {true} if a default value exists for the
@@ -2015,6 +2061,15 @@ IsVariableUsageAllowed(variableDefinition, variableUsage):
20152061
- Return {AreTypesCompatible(variableType, nullableLocationType)}.
20162062
- Return {AreTypesCompatible(variableType, locationType)}.
20172063

2064+
IsNonNullPosition(locationType, variableUsage):
2065+
2066+
- If {locationType} is a non-null type, return {true}.
2067+
- If the location of {variableUsage} is an {ObjectField}:
2068+
- Let {parentObjectValue} be the {ObjectValue} containing {ObjectField}.
2069+
- Let {parentLocationType} be the expected type of {ObjectValue}.
2070+
- If {parentLocationType} is a _OneOf Input Object_ type, return {true}.
2071+
- Return {false}.
2072+
20182073
AreTypesCompatible(variableType, locationType):
20192074

20202075
- If {locationType} is a non-null type:
@@ -2103,6 +2158,30 @@ query listToNonNullList($booleanList: [Boolean]) {
21032158
This would fail validation because a `[T]` cannot be passed to a `[T]!`.
21042159
Similarly a `[T]` cannot be passed to a `[T!]`.
21052160

2161+
Variables used for OneOf Input Object fields must be non-nullable.
2162+
2163+
```graphql example
2164+
mutation addCat($cat: CatInput!) {
2165+
addPet(pet: { cat: $cat }) {
2166+
name
2167+
}
2168+
}
2169+
2170+
mutation addCatWithDefault($cat: CatInput! = { name: "Brontie" }) {
2171+
addPet(pet: { cat: $cat }) {
2172+
name
2173+
}
2174+
}
2175+
```
2176+
2177+
```graphql counter-example
2178+
mutation addNullableCat($cat: CatInput) {
2179+
addPet(pet: { cat: $cat }) {
2180+
name
2181+
}
2182+
}
2183+
```
2184+
21062185
**Allowing Optional Variables When Default Values Exist**
21072186

21082187
A notable exception to typical variable type compatibility is allowing a

0 commit comments

Comments
 (0)