-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
RFC: OneOf Input Objects #825
base: main
Are you sure you want to change the base?
Changes from 4 commits
c385058
f6bd659
39e593c
b6741c3
d17d5ec
dca3826
7e02f5a
4111476
6754e0a
7c4c1a2
bb225f7
05fde06
e8f6145
08abf49
59cb12d
c470afb
99aa5d9
691087d
7109dbc
05ab541
6a6be52
de87d2f
57e2388
5a966f2
e78d2b5
c6cd857
d106233
87d0b22
d88d62a
a810aef
a1563a9
b45c0e4
0c9830e
c4d0b50
340594e
dbccf84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -818,6 +818,9 @@ of rules must be adhered to by every Object type in a GraphQL schema. | |
characters {"__"} (two underscores). | ||
2. The argument must accept a type where {IsInputType(argumentType)} | ||
returns {true}. | ||
3. If the field is a Oneof Field: | ||
1. The argument must be nullable. | ||
2. The argument must not have a default value. | ||
3. An object type may declare that it implements one or more unique interfaces. | ||
4. An object type must be a super-set of all interfaces it implements: | ||
1. Let this object type be {objectType}. | ||
|
@@ -832,19 +835,21 @@ IsValidImplementation(type, implementedType): | |
defined in {implementedType}. | ||
1. Let {field} be that named field on {type}. | ||
2. Let {implementedField} be that named field on {implementedType}. | ||
1. {field} must include an argument of the same name for every argument | ||
3. {field} must include an argument of the same name for every argument | ||
defined in {implementedField}. | ||
1. That named argument on {field} must accept the same type | ||
(invariant) as that named argument on {implementedField}. | ||
2. {field} may include additional arguments not defined in | ||
4. {field} may include additional arguments not defined in | ||
{implementedField}, but any additional argument must not be required, | ||
e.g. must not be of a non-nullable type. | ||
3. {field} must return a type which is equal to or a sub-type of | ||
5. {field} must return a type which is equal to or a sub-type of | ||
(covariant) the return type of {implementedField} field's return type: | ||
1. Let {fieldType} be the return type of {field}. | ||
2. Let {implementedFieldType} be the return type of {implementedField}. | ||
3. {IsValidImplementationFieldType(fieldType, implementedFieldType)} | ||
must be {true}. | ||
6. {field} must be a Oneof Field if and only if {implementedField} is a | ||
Oneof Field. | ||
|
||
IsValidImplementationFieldType(fieldType, implementedFieldType): | ||
1. If {fieldType} is a Non-Null type: | ||
|
@@ -917,6 +922,30 @@ May yield the result: | |
The type of an object field argument must be an input type (any type except an | ||
Object, Interface, or Union type). | ||
|
||
**Oneof Fields** | ||
|
||
Oneof Fields are a special variant of Object Type fields where the type system | ||
asserts that exactly one of the field's arguments must be set and non-null, all | ||
others being omitted. This is useful for representing situations where an input | ||
may be one of many different options. | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
When using the type system definition language, the `@oneOf` directive is used | ||
to indicate that a Field is a Oneof Field (and thus requires exactly one of its | ||
arguments be provided): | ||
|
||
```graphql | ||
type Query { | ||
findUser( | ||
byID: ID | ||
byUsername: String | ||
byEmail: String | ||
byRegistrationNumber: Int | ||
): User @oneOf | ||
} | ||
``` | ||
|
||
In schema introspection, the `__Field.oneArgument` field will return {true} for | ||
Oneof Fields, and {false} for all other Fields. | ||
|
||
### Field Deprecation | ||
|
||
|
@@ -1160,6 +1189,9 @@ Interface types have the potential to be invalid if incorrectly defined. | |
characters {"__"} (two underscores). | ||
2. The argument must accept a type where {IsInputType(argumentType)} | ||
returns {true}. | ||
3. If the field is a Oneof Field: | ||
1. The argument must be nullable. | ||
2. The argument must not have a default value. | ||
3. An interface type may declare that it implements one or more unique | ||
interfaces, but may not implement itself. | ||
4. An interface type must be a super-set of all interfaces it implements: | ||
|
@@ -1448,6 +1480,28 @@ define arguments or contain references to interfaces and unions, neither of | |
which is appropriate for use as an input argument. For this reason, input | ||
objects have a separate type in the system. | ||
|
||
**Oneof Input Objects** | ||
|
||
Oneof Input Objects are a special variant of Input Objects where the type | ||
system asserts that exactly one of the fields must be set and non-null, all | ||
others being omitted. This is useful for representing situations where an input | ||
may be one of many different options. | ||
|
||
When using the type system definition language, the `@oneOf` directive is used | ||
to indicate that an Input Object is a Oneof Input Object (and thus requires | ||
exactly one of its field be provided): | ||
|
||
```graphql | ||
input UserUniqueCondition @oneOf { | ||
id: ID | ||
username: String | ||
organizationAndEmail: OrganizationAndEmailInput | ||
} | ||
``` | ||
|
||
In schema introspection, the `__Type.oneField` field will return {true} for | ||
Oneof Input Objects, and {false} for all other Input Objects. | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
**Circular References** | ||
|
||
Input Objects are allowed to reference other Input Objects as field types. A | ||
|
@@ -1570,6 +1624,37 @@ Literal Value | Variables | Coerced Value | |
`{ b: $var }` | `{ var: null }` | Error: {b} must be non-null. | ||
`{ b: 123, c: "xyz" }` | `{}` | Error: Unexpected field {c} | ||
|
||
|
||
Following are examples of input coercion for a Oneof Input Object with a | ||
`String` member field `a` and an `Int` member field `b`: | ||
|
||
```graphql example | ||
input ExampleInputTagged @oneOf { | ||
a: String | ||
b: Int | ||
} | ||
``` | ||
|
||
Literal Value | Variables | Coerced Value | ||
------------------------ | ----------------------- | --------------------------- | ||
`{ a: "abc", b: 123 }` | `{}` | Error: Exactly one key must be specified | ||
`{ a: null, b: 123 }` | `{}` | Error: Exactly one key must be specified | ||
`{ b: 123 }` | `{}` | `{ b: 123 }` | ||
`{ a: $var, b: 123 }` | `{ var: null }` | Error: Exactly one key must be specified | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
`{ a: $var, b: 123 }` | `{}` | `{ b: 123 }` | ||
`{ b: $var }` | `{ var: 123 }` | `{ b: 123 }` | ||
`$var` | `{ var: { b: 123 } }` | `{ b: 123 }` | ||
`"abc123"` | `{}` | Error: Incorrect value | ||
`$var` | `{ var: "abc123" } }` | Error: Incorrect value | ||
`{ a: "abc", b: "123" }` | `{}` | Error: Exactly one key must be specified | ||
`{ b: "123" }` | `{}` | Error: Incorrect value for member field {b} | ||
`{ a: "abc" }` | `{}` | `{ a: "abc" }` | ||
`{ b: $var }` | `{}` | Error: No keys were specified | ||
`$var` | `{ var: { a: "abc" } }` | `{ a: "abc" }` | ||
`{ a: "abc", b: null }` | `{}` | Error: Exactly one key must be specified | ||
`{ b: $var }` | `{ var: null }` | Error: Value for member field {b} must be non-null | ||
`{ b: 123, c: "xyz" }` | `{}` | Error: Exactly one key must be specified | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My in meeting proposal was that this case could just be invalid at start. This L1441 in Validation file in this PR sounds like it would do just that: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are exactly the same as for input objects (which also don't specify what happens if you have multiple variables); but I'll add some for clarity. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @leebyron Good catch; that was not my intent. I have updated the PR with better validation and more examples. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've since revisited my thoughts on this and for the sake of defining types of variables on the client I've adopted the suggestion: #825 (comment)
benjie marked this conversation as resolved.
Show resolved
Hide resolved
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
**Type Validation** | ||
|
||
1. An Input Object type must define one or more input fields. | ||
michaelstaib marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
@@ -1580,6 +1665,9 @@ Literal Value | Variables | Coerced Value | |
characters {"__"} (two underscores). | ||
3. The input field must accept a type where {IsInputType(inputFieldType)} | ||
returns {true}. | ||
4. If the Input Object is a Oneof Input Object then: | ||
1. The type of the input field must be nullable. | ||
2. The input field must not have a default value. | ||
3. If an Input Object references itself either directly or through referenced | ||
Input Objects, at least one of the fields in the chain of references must be | ||
either a nullable or a List type. | ||
|
@@ -1600,11 +1688,15 @@ be used by a GraphQL service which is itself an extension of another GraphQL ser | |
Input object type extensions have the potential to be invalid if incorrectly defined. | ||
|
||
1. The named type must already be defined and must be a Input Object type. | ||
3. All fields of an Input Object type extension must have unique names. | ||
4. All fields of an Input Object type extension must not already be a field of | ||
2. All fields of an Input Object type extension must have unique names. | ||
3. All fields of an Input Object type extension must not already be a field of | ||
the original Input Object. | ||
5. Any non-repeatable directives provided must not already apply to the | ||
4. Any non-repeatable directives provided must not already apply to the | ||
original Input Object type. | ||
5. If the original Input Object is a Oneof Input Object then: | ||
1. All fields of the Input Object type extension must be nullable. | ||
2. All fields of the Input Object type extension must not have default | ||
values. | ||
|
||
|
||
## List | ||
|
@@ -1815,8 +1907,13 @@ by a validator, executor, or client tool such as a code generator. | |
GraphQL implementations should provide the `@skip` and `@include` directives. | ||
|
||
GraphQL implementations that support the type system definition language must | ||
provide the `@deprecated` directive if representing deprecated portions of | ||
the schema. | ||
provide: | ||
|
||
- the `@deprecated` directive if representing deprecated portions of the | ||
schema; | ||
- the `@oneOf` directive if representing types that require exactly one field | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(i.e. Oneof Input Objects) or fields that require exactly one argument (i.e. | ||
Oneof Fields). | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
**Custom Directives** | ||
|
||
|
@@ -1980,3 +2077,34 @@ type ExampleType { | |
oldField: String @deprecated(reason: "Use `newField`.") | ||
} | ||
``` | ||
|
||
### @oneOf | ||
|
||
```graphql | ||
directive @oneOf on INPUT_OBJECT | FIELD_DEFINITION | ||
``` | ||
|
||
The `@oneOf` directive is used within the type system definition language | ||
to indicate: | ||
|
||
- an Input Object is a Oneof Input Object, or | ||
- an Object Type's Field is a Oneof Field. | ||
|
||
```graphql example | ||
input UserUniqueCondition @oneOf { | ||
id: ID | ||
username: String | ||
organizationAndEmail: OrganizationAndEmailInput | ||
} | ||
``` | ||
|
||
```graphql example | ||
type Query { | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
findUser( | ||
byID: ID | ||
byUsername: String | ||
byEmail: String | ||
byRegistrationNumber: Int | ||
): User @oneOf | ||
} | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -147,6 +147,9 @@ type __Type { | |
|
||
# should be non-null for NON_NULL and LIST only, must be null for the others | ||
ofType: __Type | ||
|
||
# should be non-null for INPUT_OBJECT only | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
oneField: Boolean | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
type __Field { | ||
|
@@ -156,6 +159,7 @@ type __Field { | |
type: __Type! | ||
isDeprecated: Boolean! | ||
deprecationReason: String | ||
oneArgument: Boolean! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or |
||
} | ||
|
||
type __InputValue { | ||
|
@@ -336,6 +340,8 @@ Fields | |
* `name` must return a String. | ||
* `description` may return a String or {null}. | ||
* `inputFields`: a list of `InputValue`. | ||
* `oneField` must return {true} for Oneof Input Objects, {false} for all other | ||
Input Objects. | ||
* All other fields must return {null}. | ||
|
||
|
||
|
@@ -380,6 +386,8 @@ Fields | |
* `isDeprecated` returns {true} if this field should no longer be used, | ||
otherwise {false}. | ||
* `deprecationReason` optionally provides a reason why this field is deprecated. | ||
* `oneArgument` must return {true} for Oneof Fields, {false} for all other | ||
Fields. | ||
|
||
|
||
### The __InputValue Type | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -780,6 +780,20 @@ fragment missingRequiredArg on Arguments { | |
} | ||
``` | ||
|
||
#### Oneof Fields Have Exactly One Argument | ||
|
||
* For each Oneof Field in the document: | ||
* Let {arguments} be the arguments provided by the Field. | ||
* {arguments} must contain exactly one entry. | ||
* For the sole {argument} in {arguments}: | ||
* Let {value} be the value of {argument}. | ||
* {value} must not be the {null} literal. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the word literal appropriate here in case of using variables? The same question about Oneof for Input Object. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe so; I've modeled it on the language already used in this section, namely: https://spec.graphql.org/draft/#sel-LALTHHDHFFFJDAAACDJ-3S |
||
|
||
**Explanatory Text** | ||
|
||
Oneof Fields require that exactly one argument must be supplied and that | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
argument must not be null. | ||
|
||
## Fragments | ||
|
||
### Fragment Declarations | ||
|
@@ -1424,6 +1438,22 @@ arguments, an input object may have required fields. An input field is required | |
if it has a non-null type and does not have a default value. Otherwise, the | ||
input object field is optional. | ||
|
||
### Oneof Input Objects Have Exactly One Field | ||
|
||
**Formal Specification** | ||
|
||
* For each Oneof Input Object in the document: | ||
* Let {fields} be the fields provided by that Oneof Input Object. | ||
* {fields} must contain exactly one entry. | ||
* For the sole {field} in {fields}: | ||
* Let {value} be the value of {field}. | ||
* {value} must not be the {null} literal. | ||
|
||
**Explanatory Text** | ||
|
||
Oneof Input Objects require that exactly one field must be supplied and that | ||
field must not be null. | ||
|
||
|
||
## Directives | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels kinda awkward, like we're skirting around the idea of having fundamentally different types of fields without really committing to it?