-
Notifications
You must be signed in to change notification settings - Fork 257
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Rejects composition when the options for a path have different runtim… #1556
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
const witness = buildWitnessOperation(unsatisfiablePath); | ||
|
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.
Remark: removed that TODO because it is outdated, we do have more detailed error messages now. Technically unrelated to that PR though.
return types; | ||
} | ||
|
||
export function extractValidationError(error: any): ValidationError | undefined { |
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.
Remark: this method is not used within federation itself, but it's useful in some tooling (planning to use it in the update to https://github.com/apollographql/composer-tool after this patch) so I'd prefer keeping it.
@@ -177,7 +177,7 @@ class QueryPlanningTaversal<RV extends Vertex> { | |||
// Do note that we'll only need that `__typename` if there is no other selections inside `foo`, and so we might include | |||
// it unecessarally in practice: it's a very minor inefficiency though. | |||
if (operation.kind === 'FragmentElement') { | |||
this.closedBranches.push([option.paths.map(p => terminateWithNonRequestedTypenameField(p))]); | |||
this.closedBranches.push(options.map((o) => o.paths.map(p => terminateWithNonRequestedTypenameField(p)))); |
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.
Remark: this change is technically not really necessary to this patch, and in fact I'm not entirely sure if this whole branch of query planner can still be hit with the validation added (but I'm unsure and would rather keep it for now), but I noticed this while working on this patch and the new version is "more correct" (mostly, it avoids producing plans that are more inefficient than necessary if this code path is triggered).
6a74f17
to
39d8302
Compare
internals-js/src/error.ts
Outdated
@@ -301,6 +301,11 @@ const INVALID_LINK_DIRECTIVE_USAGE = makeCodeDefinition( | |||
'An application of the @link directive is invalid/does not respect the specification.' | |||
); | |||
|
|||
const SHAREABLE_WITH_MISMATCHED_RUNTIME_TYPES = makeCodeDefinition( |
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 would change this to SHAREABLE_HAS_MISMATCHED_RUNTIME_TYPES as it's a little more clear.
internals-js/src/error.ts
Outdated
@@ -301,6 +301,11 @@ const INVALID_LINK_DIRECTIVE_USAGE = makeCodeDefinition( | |||
'An application of the @link directive is invalid/does not respect the specification.' | |||
); | |||
|
|||
const SHAREABLE_WITH_MISMATCHED_RUNTIME_TYPES = makeCodeDefinition( | |||
'SHAREABLE_WITH_MISMATCHED_RUNTIME_TYPES', | |||
'A shareable field return type have mismatched possible runtime types in the subgraphs in which the field is declared. As shared fields must resolve the same way in all subgraphs, this is almost surely a mistake.' |
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.
change 'have' to 'has'
@@ -257,6 +277,19 @@ function initialSubgraphPaths(kind: SchemaRootKind, subgraphs: QueryGraph): Root | |||
return subgraphs.outEdges(root).map(e => initialState.add(subgraphEnteringTransition, e, noConditionsResolution)); | |||
} | |||
|
|||
function possibleRuntimeTypeNamesSorted(path: RootPath<Transition>): string[] { | |||
const types = path.tailPossibleRuntimeTypes().map((o) => o.name); |
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.
Is this already deduped?
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.
It is. And it does take a small effort to make sure so I commented as much on the method. But essentially, tailPossibleRuntimeTypes
in GraphPath
is only updated through the (private) updateRuntimeTypes
method in graphPaths.ts
, and the only place we "push" there is guarded by I first check to ensure the element is not already there.
@@ -112,3 +112,57 @@ describe('@requires', () => { | |||
]); | |||
}); | |||
}); | |||
|
|||
|
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 the comments of validation.ts you have a union as an example, did you want to have that as an explicit test as well or are the interface / union examples sufficently similar?
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.
You're right, it's worth having both and pulled the example from validation.ts
into a proper test (which also ensure the comment is actually correct, which it wasn't cause I had forgotten some @shareable
:)).
composition-js/src/validate.ts
Outdated
for (const path of newSubgraphPaths) { | ||
// Note: we add spacing between elements because we're going use it if we display an error. This doesn't impact our set equality though | ||
// since type names can have neither comma or space in graphQL. | ||
runtimeTypesToSubgraphs.add(possibleRuntimeTypeNamesSorted(path).join(', '), path.tail.source); |
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.
Trying to follow this, but I'm not sure if I'm getting it right. For the following example:
# subgraph 1
Query {
a: A!
}
interface A {
x: Int
}
type B implements A {
x: Int
}
#subgraph 2
Query {
a: A!
}
interface A {
x: Int
}
type C implements A {
x: Int
}
The things that are being compared are the internals of types B and C to make sure they match, so the string you're building will look something like "x:Int"? Is that right? If so, I think it looks good, if otherwise I may have more questions.
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.
So no. In that example, that code path will kick in after we've look at field Query.a
, and we will essentially have 2 "options" (2 newSubgraphPaths
), one per subgraph, but each one "tail" will be on interface A
. So for the 1st path, possibleRuntimeTypeNamesSorted
will return B
, while it will return C
. And because those are different, we'll return the error. Does that help?
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.
ok, we want that case to error because the types are named differently, so it doesn't matter.
39d8302
to
38ecf4a
Compare
✅ Deploy Preview for apollo-federation-docs ready!
To edit notification comments on pull requests, go to your Netlify site settings. |
38ecf4a
to
fcda450
Compare
Unfortunately, in the lead up to federation 2.0, this PR fall through the crack and never got finished/committed. I'd like to resurrect this and get it committed ASAP however, because I think this can really help people not getting That said, upon further reflexion (and a bit influenced by the fact this missed 2.0), I changed the check a bit to make it a bit more permissive. That is the rebased version:
|
fcda450
to
ffd2943
Compare
…e types During composition validation, a path of the supergraph can have multiple valid options in subgraphs due to shared fields. When that happens, this patch validates that the possible runtime types of the various options are the same. The rational being that shared fields should resolve the same way in all subgraphs, so allowing 2 differen solution of shared field to have different set of possible runtime types is a dangerous proposition: to be correct, users would have make sure they only ever return value for that field that are in the intersection of those possible runtime types. Instead of asking users to reason about this, forcing the sets of possible runtime types to always be the same (for a given shared field) ensures any returned value is always in the intersection (since the intersection of a set with itself is itself). Fixes #1375
ffd2943
to
b195e46
Compare
This commit fixes two main problems: 1. merging a shareable field whose return type was an `@interfaceObject` was not working properly. 2. some metadata was not properly computed on a path added for `@interfaceObject` and this led to a potential assertion errors during composition validation on some cases. For the merging of shareable fields, the main issue was that the code checking if 2 types are "the same" was relying on the type "kind", which was fine before but broke with `@interfaceObject` in the sense that some type type T can be an interface type in one subgraph but an object type in another (through `@interfaceObject`) and we were not considering those "the same type". But with that fixed, a small issue remained in that the check added in apollographql#1556 had not been updated for `@interfaceObject` and needed to ignore those. For the 2nd issue, the core bug was that when building a `GraphPath`, we keep track of the edge that made us enter the current graph, and some assertion is essentially checking that there is no "key" edges between that edge and the end of the path, but that "edge entering the subgraph" info was not properly set on a path added for `@interfaceObject` and that triggered the assertion. This commit fix this issue, but this was also highlighting some ineffeciency whereby we were generating unecessary paths (path moving to a subgraph and then moving back again right away), so the patch also fix that inefficiency.
This commit fixes two main problems: 1. merging a shareable field whose return type was an `@interfaceObject` was not working properly. 2. some metadata was not properly computed on a path added for `@interfaceObject` and this led to a potential assertion errors during composition validation on some cases. For the merging of shareable fields, the main issue was that the code checking if 2 types are "the same" was relying on the type "kind", which was fine before but broke with `@interfaceObject` in the sense that some type type T can be an interface type in one subgraph but an object type in another (through `@interfaceObject`) and we were not considering those "the same type". But with that fixed, a small issue remained in that the check added in #1556 had not been updated for `@interfaceObject` and needed to ignore those. For the 2nd issue, the core bug was that when building a `GraphPath`, we keep track of the edge that made us enter the current graph, and some assertion is essentially checking that there is no "key" edges between that edge and the end of the path, but that "edge entering the subgraph" info was not properly set on a path added for `@interfaceObject` and that triggered the assertion. This commit fix this issue, but this was also highlighting some ineffeciency whereby we were generating unecessary paths (path moving to a subgraph and then moving back again right away), so the patch also fix that inefficiency.
…e types
During composition validation, a path of the supergraph can have
multiple valid options in subgraphs due to shared fields. When
that happens, this patch validates that the possible runtime types
of the various options are the same. The rational being that
shared fields should resolve the same way in all subgraphs, so
allowing 2 differen solution of shared field to have different set
of possible runtime types is a dangerous proposition: to be correct,
users would have make sure they only ever return value for that field
that are in the intersection of those possible runtime types.
Instead of asking users to reason about this, forcing the sets of
possible runtime types to always be the same (for a given shared field)
ensures any returned value is always in the intersection (since the
intersection of a set with itself is itself).
Fixes #1375