SemanticNonNull type (null only on error)
At a glance​
- Identifier: #1065
- Stage: RFC0: Strawman
- Champion: @benjie
- PR: SemanticNonNull type (null only on error)
- Related:
Timeline​
- Commit pushed: Add IsValidImplementationFieldType updates on 2024-01-03 by @benjie
- Spec PR created on 2023-11-24 by benjie
- Commit pushed: Change name to 'SemanticNonNull' and syntax to bang prefix on 2023-11-24 by @benjie
- 3 commits pushed on 2023-10-04:
TL;DR: Introduces a new type wrapper, Semantic-Non-Null, which represents that a value will not be null unless an error happens, and if an error does happen then this
null
does not bubble.The problem​
GraphQL schema designers must use non-nullable types sparingly because if a non-nullable type were to raise an error then the entire selection set it is within will be destroyed, leading to clients receiving less usable data and making writing the results to a normalized cache a dangerous action. Because of this, nullable-by-default is a best practice in GraphQL, and non-null type wrappers should only be used for fields that the schema designer is confident will never raise an error - not just in the current schema, but in all future schemas.
Many GraphQL consumers choose to ignore the entire response from the server when any error happens, one reason for this is because the null bubbling behavior makes writing to normalized caches dangerous. For these users, when an error doesn't happen, the nullable fields they are dealing with can be frustrating because their type generation requires them to handle the null case even if it may never happen in practice, which can lead to a lot of unnecessary code that will never execute. There is currently no way for the type generators to know that a field will never be null unless there's an associated error.
The solution​
We can categorise that there are effectively two types of
null
:
- Error
null
: where a position isnull
and there's a related error (with matching or prefixed path) in theerrors
list - indicates that something went wrong.- Semantic
null
: where a position isnull
and there is no related error - this data truly is null (e.g. a user having not yet set their avatar may haveavatar: null
; this is not an error).This PR introduces a new wrapper type in addition to List and Non-Null, called Semantic-Non-Null. The Semantic-Non-Null type indicates that the field will never be a semantic
null
- it will not benull
in the normal course of business, but can be null only if accompanied by an error in theerrors
list (i.e. an "errornull
"). Thus a client that throws out all responses with errors will never see anull
in this position. Also, critically, anynull
raised by this field will not bubble and thus if an error is found with the exact path to thisnull
then it is safe to store the result (including the error) into a normalized cache.In SDL the Semantic-Non-Null wrapper is currently represented by a
!
prefix (as opposed to the!
suffix for a strict Non-Null).Thus we have the following:
# Type description Syntax Result values 1
Unadorned String String
string, or error null
, or semanticnull
2
Semantic-Non-Null String !String
string, or error null
3
(Strict-)Non-Null String String!
string Note that
1
and3
above are exactly the same as in the current GraphQL specification, this PR introduces2
which sits in the middle.Backwards compatibility​
All existing schemas are automatically supported because the meaning of
String
andString!
is unchanged.To ensure that all existing clients are automatically supported, this PR introduces the
includeSemanticNonNull
argument on__Field.type
which defaults tofalse
. Clients that do not passincludeSemanticNonNull: true
will see all Semantic-Non-Null types stripped, which will have the effect of making them appear as if they were the unadorned types. This is safe, since it means these clients will need to handle both error nulls and semantic nulls (as they traditionally would have) even though we know that a semantic null will never happen in practice.All existing GraphQL documentation, tutorials, examples, and everything else we've built over the last 8 years remains valid since the meaning of
String
andString!
are unchanged.History​
This PR is almost identical to #1048, but it changes the name of the new type wrapper from Null-Only-On-Error to Semantic-Non-Null, and changes the syntax from
String*
to!String
. It addresses the True Nullability Schema discussion raised by @captbaritone and incorporates/adapts some of the terminology from @leebyron's Strict Semantic Nullability proposal.