-
Notifications
You must be signed in to change notification settings - Fork 6
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
@semanticNonNull
/@catch
: default levels to [0] instead of null
#42
Conversation
``` | ||
|
||
`field` is used for client applications that do not control the base schema and must use type extensions. | ||
When used on a field definition, `field` must be null. |
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.
Unrelated to the main change in this PR, but...
I'm guessing you considered this already, but it would be possible to let regular GraphQL schema validation enforce this invariant for us by making a separate directive for the case where you specify a field, and then have that directive require a field be specified, and removing the argument from the field version.
As it stands we need ad-hoc validation that you do specify a field when used on a type, and that you don't specify a field when used on a field.
directive @semanticNonNull(level: Int = 0) repeatable on FIELD_DEFINITION
directive @semanticNonNullField(field: String!, level: Int = 0) repeatable on OBJECT
That said, I can see that having two directives is also cumbersome.
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.
Makes sense. The less custom validation I have to do, the happier I am. @semanticNonNullField(field: String!)
feels like a mouthful of "fields" though. Other suggestions:
extend type User @semanticNonNullField(name: "friends", level: 0)
extend type User @addSemanticNonNull(forField: "friends", level: 0)
Also thinking about this, we could also remove the repeatable
from the regular @semanticNonNull
:
# pass all your levels all at once
directive @semanticNonNull(levels: [Int] = [0]) on FIELD_DEFINITION
# this ones needs to be repeatable for different fields. Which means we need validation that different directives for the same field do not conflict but 🤷
directive @semanticNonNullField(name: String, levels: [Int] = [0]) repeatable on OBJECT | INTERFACE
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.
Yeah, if we make levels plural, then we can remove the repeatable for the FIELD_DEFINITION variant. @addSemanticNonNull
seem fine!
So the remaining validation we need to do:
- The field/level(s) being referenced exist and are currently nullable
- For
@addSemanticNonNull
, validate that the field exists - Validate type compatibility with interfaces
In theory you could end up with a type getting marked as semantically non null in multiple ways. Probably that's okay? As long as it does not confuse tools:
type User @addSemanticNonNull(forField: "name") {
name: String @semanticNonNull
}
Or
type User {
friends: [User] @semanticNonNull(levels: [1,1,1,])
}
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.
In theory you could end up with a type getting marked as semantically non null in multiple ways.
More validation to be done but I think it's ok.
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.
Name nit: I find the word add
to be a bit off for a directive because in a way all directives add something. semanticNonNullField
works better for me but not a strong opinion.
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.
Agree directives are declarative and shouldn't start with a verb. I settled with @semanticNonNullField(name: String, levels: [Int])
in ba09b50, let me know what you think.
Also nitpick but it should really be @semanticNonNullPosition(field: String, levels: [Int])
but the spec uses field instead of position (or something else) in a lot of places so I kept Field
for consistency even if it's a bit wrong.
``` | ||
|
||
`field` is used for client applications that do not control the base schema and must use type extensions. | ||
When used on a field definition, `field` must be null. |
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.
Name nit: I find the word add
to be a bit off for a directive because in a way all directives add something. semanticNonNullField
works better for me but not a strong opinion.
@semanticNonNull
/@catch
: default level to 0 instead of null@semanticNonNull
/@catch
: default levels to [0] instead of null
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.
One optional minor nit, but otherwise looks good!
Something I realized by making {
user {
# this is not possible
# throw if the list errors but recover from item errors
friends @catch(to: THROW, levels: [0]) @catch(to: NULL, levels: [1])
}
} Maybe not too bad but maybe the |
Summary: This PR adds experimental support for `semanticNonNull` as described in apollographql/specs#42. Which is part of a broader effort to explore semantic nullability in GraphQL as explored in [RFC: SemanticNonNull type (null only on error) ](graphql/graphql-spec#1065). This directive-based approach should allow us to experiment with the concepts and identify issues as we work to understand the viability of semantic nullability in GraphQL. ## Experimental As this is still an experimental implementation, it's designed to be minimally invasive rather than ideal in terms of architecture or performance. As the feature/RFCs stabilize I would imagine we would bake this into the schema crate and data structures as a first class concept. This flag will not be broadly safe to enable in Relay since by default fields that are null due to error are still surfaced to the user. This is only safe to enable if: 1. Your network layer discards all payloads that have any field errors 2. You enable our [explicit error handling feature](#4416), which is still itself experimental. ## Missing Pieces - [ ] Documentation about how to use this feature (purposeful, since this is experimental) - [ ] Support for `semanticNonNullField` which allows a patching an existing type to define it's field's semantic nullability - [ ] Validation - [ ] Invalid use of `levels` will simply panic. - [ ] Uses of `semanticNonNullField` will simply be ignored - [ ] There is no schema validation ensuring interface types have type-compatible semantic nullability Pull Request resolved: #4601 Test Plan: ``` cargo test ``` I also spun up a version of [`grats-relay-example`](https://github.com/captbaritone/grats-relay-example) using Grat's [experimental support for `semanticNonNull`](https://grats.capt.dev/docs/guides/strict-semantic-nullability/) and was able to see it working end to end. https://github.com/facebook/relay/assets/162735/dc979a58-95f3-4e55-9d9b-577afdd798ca Reviewed By: alunyov Differential Revision: D53191255 Pulled By: captbaritone fbshipit-source-id: c09333f2b9475315d81792d33947fd908001c021
Because this is bumping the version to0.2
, it's a new file but you can see the diff in this commitNot anymore because there have been additional changes, you'll have to read the full new text, sorry!