-
Notifications
You must be signed in to change notification settings - Fork 47
Add definition_origin field to type definitions
#1009
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
base: main
Are you sure you want to change the base?
Conversation
| pub directives: DirectiveList, | ||
| /// Non-extension definition origin, if exists. | ||
| /// - We hold the origin here, so its reference can be returned from `iter_origins()`. | ||
| pub definition_origin: Option<ComponentOrigin>, |
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.
As you already called out, this will be a breaking change to the API. One thing I'd like to explore is if this is the right pattern for including this. Typically, we encode a Node plus a ComponentOrigin by wrapping a type in Component<T>, which is why we can get the origin for most of the children in the AST. Since the concept of origin is somewhat orthogonal to the data stored in each type, I think we should consider changing this to the following:
Schema::schema_definitionshould be aComponent<SchemaDefinition>instead ofNode<SchemaDefinition>- Similarly, we should update
ExtendedTypeto holdComponent<T>instead ofNode<T>for each type
I think that would be better aligned with the current implementation of this library. I also have a couple other notes that I'm curious if you have an opinion on:
- Knowing how we're consuming this downstream, I think the actual thing we want to check is if the
ExtendedTypewe get came from an extension or not (i.e. I think it's a logical bug, or at least a wasted check, to check the origins of the children in the AST). This may or may not inform the final solution here, but I just wanted to call that out. - One unexpected thing I came across yesterday was that
ExtensionIdis actually anArc-wrapped source span. I naively assumed it would be something like anAtomicUsize. This doesn't affect the current changes, but it does have bearing on my next comment. - Since we're adding
ComponentOriginto more of the places where we are holdingNode<T>, and whatever solution we come to will likely be a breaking API change, I'm wondering if this is the right time to consolidateComponent<T>andNode<T>. I don't think naively adding the origin as-is to the current node header would be performant enough, since historically we've cared about how many bytes of overhead we add to eachNode. However, I could see someAtomicUsizeor even a simple extension/non-extension boolean flag giving us enough information for our use case.
I'll stop there. Curious to get your thoughts.
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.
There's also some context on a similar issue regarding DirectiveList in #851
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.
Thanks for bringing up the #851.
Schema elements combine implicit def, explicit def and multiple extensions, thus naturally can have multiple origins or nothing at all (if it's all implicit). We could elect one origin to represent the Component, but unfortunately Component has no option to be "implicit" at this time.
If we used Component across the board, then the origin should be optional or has a new variant like Builtin and/or Implicit.
BTW, the origin election would be on this order: builtin/implicit < extension < definition. So, the highest origin will represent the element. That's enough info to fix this PR's issue without adding definition_origin field.
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.
I'm not entirely sure what you mean by implicit in this case. At least in terms of how ComponentOrigin is currently set up, the only options are Definition or Extension. In the usual case, a type's origin should always be Definition, but we run into the case where it may only have extension definitions when we use the adopt_orphan_extensions option.
What I'm proposing is that we use ComponentOrigin::ExtensionId(_) to capture that case instead of definition_origin: None. I think that's more idiomatic with the current patterns in this crate.
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.
A schema element like built-in types may not have definition nor extensions. We won't even have an extension id to store.
| schema_definition: Node::new(SchemaDefinition { |
Alternatively, we can treat implicit definitions to be the "Definition", but distinguished by Node::is_built-in(). But, we need to check that in iter_origins().
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.
Thinking more about it, it will work without adding built-in/implicit variants.
Previously, I thought a built-in type like String could be extended with or without a base definition and only the latter is EXTENSION_WITH_NO_BASE error. That would mean that we need to tell built-in definition from explicit base definition.
However, I realized that situation won't happen:
- Schema definitions are always allowed to be extended without a base
- Thus, (built-in def + extension) and (explicit def + extension) behave the same.
- Built-in types are not allowed to be extended at all (at least JS federation prevents it).
- So, built-in types won't cause
EXTENSION_WITH_NO_BASEerrors anyways. - Rust composition allows to extend built-in scalar types at the moment, but built-in types are excluded from
EXTENSION_WITH_NO_BASEchecks.
- So, built-in types won't cause
|
#1012 is an alternative proposal that does not break API. |
Motivation
This PR fixes an issue where
iter_origins()methods fail to return theDefinitionorigin, even if the element has a (non-extension) definition. This can happen with schema definition and most type definitions.Example
iter_origin()method on the typeTonly returns anExtension, notDefinition. That's becausetype Tdoes not have fields nor directive applications and the current implementation does not record origins at all in this case.Fix
This PR adds
definition_originfield toSchemaDefinitionand other type definition structs likeScalarandObjectType. Thedefinition_originfield is expected to haveSome((ComponentOrigin::Definition)value if a schema element has a non-extension definition. Otherwise, it is expected to haveNonevalue.The field actually hold a
ComponentOriginvalue, instead of beingbooltype, becauseiter_origins()method is expected to return a reference to aComponentOriginvalue and the field allows the method to return a reference to an object held byself.Note: Unfortunately, this PR would be a breaking change.
Downstream fix PR: apollographql/router#8475