Skip to content

Commit f958fb9

Browse files
author
Sylvain Lebresne
authored
Improves error message for misspelled override (#2181)
When mispelling the `from` of an `@override`, one can easily run into an error about the field needing to be "shareable", which doesn't help locate the actual problem easily. This patch detect this situation and provide an additional hint in the error message to hopefully make the root cause easier to locate.
1 parent b47c430 commit f958fb9

File tree

4 files changed

+74
-7
lines changed

4 files changed

+74
-7
lines changed

composition-js/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ This CHANGELOG pertains only to Apollo Federation packages in the 2.x range. The
44

55
## vNext
66

7+
- Improves error message to help with misspelled source of an `@override` [PR #2181](https://github.com/apollographql/federation/pull/2181).
8+
79
## 2.1.2
810

911
- Fix composition of repeatable custom directives [PR #2136](https://github.com/apollographql/federation/pull/2136)

composition-js/src/__tests__/compose.test.ts

+33
Original file line numberDiff line numberDiff line change
@@ -2071,6 +2071,39 @@ describe('composition', () => {
20712071
['INVALID_FIELD_SHARING', 'Non-shareable field "E.b" is resolved from multiple subgraphs: it is resolved from subgraphs "subgraphA" and "subgraphB" and defined as non-shareable in subgraph "subgraphA"'],
20722072
]);
20732073
});
2074+
2075+
it('include hint in error message on shareable error due to target-less @override', () => {
2076+
const subgraphA = {
2077+
typeDefs: gql`
2078+
type Query {
2079+
e: E
2080+
}
2081+
2082+
type E @key(fields: "id") {
2083+
id: ID!
2084+
a: Int @override(from: "badName")
2085+
}
2086+
`,
2087+
name: 'subgraphA',
2088+
};
2089+
2090+
const subgraphB = {
2091+
typeDefs: gql`
2092+
type E @key(fields: "id") {
2093+
id: ID!
2094+
a: Int
2095+
}
2096+
`,
2097+
name: 'subgraphB',
2098+
};
2099+
2100+
const result = composeAsFed2Subgraphs([subgraphA, subgraphB]);
2101+
2102+
expect(result.errors).toBeDefined();
2103+
expect(errors(result)).toStrictEqual([
2104+
['INVALID_FIELD_SHARING', 'Non-shareable field "E.a" is resolved from multiple subgraphs: it is resolved from subgraphs "subgraphA" and "subgraphB" and defined as non-shareable in all of them (please note that "E.a" has an @override directive in "subgraphA" that targets an unknown subgraph so this could be due to misspelling the @override(from:) argument)'],
2105+
]);
2106+
});
20742107
});
20752108

20762109
it('handles renamed federation directives', () => {

composition-js/src/merging/merge.ts

+37-7
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,18 @@ export type CompositionOptions = {
9494
allowedFieldTypeMergingSubtypingRules?: SubtypingRule[]
9595
}
9696

97+
type FieldMergeContextProperties = {
98+
usedOverridden: boolean,
99+
unusedOverridden: boolean,
100+
overrideWithUnknownTarget: boolean,
101+
}
102+
97103
// for each source, specify additional properties that validate functions can set
98104
class FieldMergeContext {
99-
_props: { usedOverridden: boolean, unusedOverridden: boolean }[];
105+
_props: FieldMergeContextProperties[];
100106

101107
constructor(sources: unknown[]) {
102-
this._props = (new Array(sources.length)).fill(true).map(_ => ({ usedOverridden: false, unusedOverridden: false }));
108+
this._props = (new Array(sources.length)).fill(true).map(_ => ({ usedOverridden: false, unusedOverridden: false, overrideWithUnknownTarget: false}));
103109
}
104110

105111
isUsedOverridden(idx: number) {
@@ -110,6 +116,10 @@ class FieldMergeContext {
110116
return this._props[idx].unusedOverridden;
111117
}
112118

119+
hasOverrideWithUnknownTarget(idx: number) {
120+
return this._props[idx].overrideWithUnknownTarget;
121+
}
122+
113123
setUsedOverridden(idx: number) {
114124
this._props[idx].usedOverridden = true;
115125
}
@@ -118,7 +128,11 @@ class FieldMergeContext {
118128
this._props[idx].unusedOverridden = true;
119129
}
120130

121-
some(predicate: ({ usedOverridden, unusedOverridden }: { usedOverridden: boolean, unusedOverridden: boolean }) => boolean): boolean {
131+
setOverrideWithUnknownTarget(idx: number) {
132+
this._props[idx].overrideWithUnknownTarget = true;
133+
}
134+
135+
some(predicate: (props: FieldMergeContextProperties) => boolean): boolean {
122136
return this._props.some(predicate);
123137
}
124138
}
@@ -947,10 +961,11 @@ class Merger {
947961

948962
// for each subgraph that has an @override directive, check to see if any errors or hints should be surfaced
949963
subgraphsWithOverride.forEach((subgraphName) => {
950-
const { overrideDirective } = subgraphMap[subgraphName];
964+
const { overrideDirective, idx } = subgraphMap[subgraphName];
951965
const sourceSubgraphName = overrideDirective?.arguments()?.from;
952966
const overridingSubgraphASTNode = overrideDirective?.sourceAST ? addSubgraphToASTNode(overrideDirective.sourceAST, subgraphName) : undefined;
953967
if (!this.names.includes(sourceSubgraphName)) {
968+
result.setOverrideWithUnknownTarget(idx);
954969
const suggestions = suggestionList(sourceSubgraphName, this.names);
955970
const extraMsg = didYouMean(suggestions);
956971
this.hints.push(new CompositionHint(
@@ -979,8 +994,8 @@ class Merger {
979994
const fromIdx = this.names.indexOf(sourceSubgraphName);
980995
const fromField = sources[fromIdx];
981996
const { result: hasIncompatible, conflictingDirective, subgraph } = this.overrideConflictsWithOtherDirective({
982-
idx: subgraphMap[subgraphName].idx,
983-
field: sources[subgraphMap[subgraphName].idx],
997+
idx,
998+
field: sources[idx],
984999
subgraphName,
9851000
fromIdx: this.names.indexOf(sourceSubgraphName),
9861001
fromField: sources[fromIdx],
@@ -1078,8 +1093,23 @@ class Merger {
10781093
const nonShareables = shareableSources.length > 0
10791094
? printSubgraphNames(nonShareableSources.map((s) => this.names[s]))
10801095
: 'all of them';
1096+
1097+
// An easy-to-make error that can lead here is the mispelling of the `from` argument of an @override. Because in that case, the
1098+
// @override will essentially be ignored (we'll have logged a warning, but the error we're about to log will overshadow it) and
1099+
// the 2 field insteances will violate the sharing rules. But because in that case the error is ultimately with @override, it
1100+
// can be hard for user to understand why they get a shareability error, so we detect this case and offer an additional hint
1101+
// at what the problem might be in the error message (note that even if we do find an @override with a unknown target, we
1102+
// cannot be 100% sure this is the issue, because this could also be targeting a subgraph that has just been removed, in which
1103+
// case the shareable error is legit; so keep the shareabilty error with a strong hint is hopefully good enough in practice).
1104+
// Note: if there is multiple non-shareable fields with "target-less overrides", we only hint about one of them, because that's
1105+
// easier and almost surely good enough to bring the attention of the user to potential typo in @override usage.
1106+
const subgraphWithTargetlessOverride = nonShareableSources.find((s) => mergeContext.hasOverrideWithUnknownTarget(s));
1107+
let extraHint = '';
1108+
if (subgraphWithTargetlessOverride !== undefined) {
1109+
extraHint = ` (please note that "${dest.coordinate}" has an @override directive in "${this.names[subgraphWithTargetlessOverride]}" that targets an unknown subgraph so this could be due to misspelling the @override(from:) argument)`;
1110+
}
10811111
this.errors.push(ERRORS.INVALID_FIELD_SHARING.err(
1082-
`Non-shareable field "${dest.coordinate}" is resolved from multiple subgraphs: it is resolved from ${printSubgraphNames(resolvingSubgraphs)} and defined as non-shareable in ${nonShareables}`,
1112+
`Non-shareable field "${dest.coordinate}" is resolved from multiple subgraphs: it is resolved from ${printSubgraphNames(resolvingSubgraphs)} and defined as non-shareable in ${nonShareables}${extraHint}`,
10831113
{ nodes: sourceASTs(...allResolving) },
10841114
));
10851115
}

gateway-js/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ This CHANGELOG pertains only to Apollo Federation packages in the 2.x range. The
44

55
## vNext
66

7+
- Improves error message to help with misspelled source of an `@override` [PR #2181](https://github.com/apollographql/federation/pull/2181).
8+
79
## 2.1.3
810

911
- Fix building subgraph selections using the wrong underlying schema [PR #2155](https://github.com/apollographql/federation/pull/2155).

0 commit comments

Comments
 (0)