-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
The issue
Here's a link to the specification for not-null: http://facebook.github.io/graphql/June2018/#sec-Input-Objects
It says:
Otherwise, if the field is not required, then no entry is added to the coerced unordered map.
This means that we have an opportunity to behave differently in our application code based on whether the user explicitly posts null for a field, or simply omits the field. However, we have no way of restricting the options available to a user.
Consider the following schema:
input PersonInput {
id: ID!
name: String # Does not support partial updates - i.e. null or omission results in this field being set to null in the database
hobby: String # Supports partial updates - i.e. null means to unset this field, omission means to leave the existing value as-is
}
type Person {
# ... omitted for brevity
}
type Mutation {
updatePerson(input: PersonInput): Person
}
Consider the following scenarios:
Scenario 1 - User wants to blank out all values for a person
Option 1 - Setting all fields to null
The user posts the following object:
{
"id": 1,
"name": null,
"hobby": null
}
I think this is the most idiomatic approach a user would use. The server does exactly what the user wanted.
Option 2 - Omitting all fields
The user posts the following object:
{
"id": 1
}
This doesn't feel like a natural thing to do. The server nulls out the name, but the hobby is left as-is. The user didn't achieve what they wanted, but they will be left wondering why. They will probably write a more specific query and maybe file a bug against the API saying the hobby field doesn't work as expected.
Scenario 2 - User wants to blank out the hobby for a person
Option 1 - Providing the existing name, with the hobby set to null
The user posts the following object:
{
"id": 1,
"name": "John",
"hobby": null
}
I think this is the most idiomatic approach a user would use. The server does exactly what the user wanted.
Option 2 - Providing the existing name, with the hobby omitted
The user posts the following object:
{
"id": 1,
"name": "John"
}
This doesn't feel like a natural thing to do. The server does not blank out the hobby. The user would probably retry the query using option 1 at this point.
Option 3 - Just setting the hobby to null
The user posts the following object:
{
"id": 1,
"hobby": null
}
This feels natural, especially in the "GraphQL" world where the API can constantly evolve and new fields can be added. The server nulls out the hobby as expected. However, it also nulls out the name, which the user did not expect.
Scenario 3 - User wants to blank out the name for a person
Option 1 - Providing the existing hobby, with the name set to null
The user posts the following object:
{
"id": 1,
"name": null,
"hobby": "Skydiving"
}
I think this is the most idiomatic approach a user would use. The server does exactly what the user wanted.
Option 2 - Providing the existing hobby, with the name omitted
The user posts the following object:
{
"id": 1,
"hobby": "Skydiving"
}
This doesn't feel like a natural thing to do. However, the server does exactly what the user wanted.
Option 3 - Just setting the name to null
The user posts the following object:
{
"id": 1,
"name": null
}
This feels natural, especially in the "GraphQL" world where the API can constantly evolve. The server does exactly what the user wanted.
I have not illustrated examples of the scenarios regarding a field which is always mandatory but can be omitted from an update to retain its existing value.
Description
The following cases illustrate that there are several points of ambiguity in our API:
- Scenario 1 - Option 2 - quite painful
- Scenario 2 - Option 2 - not that much of a pain point
- Scenario 2 - Option 3 - quite painful
It's easy to turn around at this point based on these trivial examples and say "Well why the hell would you do that for? Why not be consistent?"
The reason we are facing this dilemma is because we have many mutations (none of which support partial updates, i.e. all fields are always required explicitly) and our consumers want some APIs to start supporting partial updates. At the moment, some fields are optional, so both null AND omission translate to "blank it out". However, we would need to start treating omission differently (i.e. "leave as-is"). Without excessive comments in our documentation and validation in our API, our API will be difficult to understand and seem "inconsistent" to end users. It would be great if we could define input types like the following:
input ExampleInput {
id: ID!
field1: String | null # Can be a string or null, but must always be provided (i.e. does not support partial updates). This would be a solution to the scenarios I listed above.
field2?: String! # Can be a string (to update the value) or omitted (to leave as-is), but can't be null. This would be a solution to the scenario I briefly mentioned above in bold but did not provide examples for.
}
@trevorah @peterstarling @joaojeronimo @craigbilner @Belema @jackharvey1 You guys might be interested in this