Skip to content

Commit aac2893

Browse files
authored
feat: Add the schema coordinate to all CompositionHints in composition-js (#2658)
1 parent db90ba1 commit aac2893

File tree

7 files changed

+116
-47
lines changed

7 files changed

+116
-47
lines changed

.changeset/fuzzy-vans-hide.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@apollo/composition": minor
3+
---
4+
5+
Includes an optional Schema Coordinate field in the Composition Hints returned by composition
6+

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

+86-43
Large diffs are not rendered by default.

composition-js/src/composeDirectiveManager.ts

+3
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ export class ComposeDirectiveManager {
143143
this.pushHint(new CompositionHint(
144144
HINTS.DIRECTIVE_COMPOSITION_INFO,
145145
`Non-composed core feature "${coreIdentity}" has major version mismatch across subgraphs`,
146+
undefined,
146147
this.coreFeatureASTs(coreIdentity),
147148
));
148149
raisedHint = true;
@@ -174,6 +175,7 @@ export class ComposeDirectiveManager {
174175
this.pushHint(new CompositionHint(
175176
HINTS.DIRECTIVE_COMPOSITION_INFO,
176177
`Directive "@${directive.name}" should not be explicitly manually composed since it is a federation directive composed by default`,
178+
directive,
177179
composeInstance.sourceAST ? {
178180
...composeInstance.sourceAST,
179181
subgraph: sg.name,
@@ -390,6 +392,7 @@ export class ComposeDirectiveManager {
390392
this.pushHint(new CompositionHint(
391393
HINTS.DIRECTIVE_COMPOSITION_WARN,
392394
`Composed directive "@${name}" is named differently in a subgraph that doesn't export it. Consistent naming will be required to export it.`,
395+
undefined,
393396
subgraphsWithDifferentNaming
394397
.map((subgraph : Subgraph): SubgraphASTNode | undefined => {
395398
const ast = subgraph.schema.coreFeatures?.getByIdentity(items[0].feature.url.identity)?.directive.sourceAST;

composition-js/src/hints.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SubgraphASTNode } from "@apollo/federation-internals";
1+
import { NamedSchemaElement, SubgraphASTNode } from "@apollo/federation-internals";
22
import { printLocation } from "graphql";
33

44
export enum HintLevel {
@@ -231,15 +231,18 @@ export const HINTS = {
231231

232232
export class CompositionHint {
233233
public readonly nodes?: readonly SubgraphASTNode[];
234+
public readonly coordinate?: string;
234235

235236
constructor(
236237
readonly definition: HintCodeDefinition,
237238
readonly message: string,
239+
readonly element: NamedSchemaElement<any, any, any> | undefined,
238240
nodes?: readonly SubgraphASTNode[] | SubgraphASTNode
239241
) {
240242
this.nodes = nodes
241243
? (Array.isArray(nodes) ? (nodes.length === 0 ? undefined : nodes) : [nodes])
242244
: undefined;
245+
this.coordinate = element?.coordinate;
243246
}
244247

245248
toString(): string {

composition-js/src/merging/merge.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,7 @@ class Merger {
11661166
this.hints.push(new CompositionHint(
11671167
HINTS.FROM_SUBGRAPH_DOES_NOT_EXIST,
11681168
`Source subgraph "${sourceSubgraphName}" for field "${dest.coordinate}" on subgraph "${subgraphName}" does not exist.${extraMsg}`,
1169+
dest,
11691170
overridingSubgraphASTNode,
11701171
));
11711172
} else if (sourceSubgraphName === subgraphName) {
@@ -1182,6 +1183,7 @@ class Merger {
11821183
this.hints.push(new CompositionHint(
11831184
HINTS.OVERRIDE_DIRECTIVE_CAN_BE_REMOVED,
11841185
`Field "${dest.coordinate}" on subgraph "${subgraphName}" no longer exists in the from subgraph. The @override directive can be removed.`,
1186+
dest,
11851187
overridingSubgraphASTNode,
11861188
));
11871189
} else {
@@ -1224,20 +1226,23 @@ class Merger {
12241226
this.hints.push(new CompositionHint(
12251227
HINTS.OVERRIDE_DIRECTIVE_CAN_BE_REMOVED,
12261228
`Field "${dest.coordinate}" on subgraph "${subgraphName}" is not resolved anymore by the from subgraph (it is marked "@external" in "${sourceSubgraphName}"). The @override directive can be removed.`,
1229+
dest,
12271230
overridingSubgraphASTNode,
12281231
));
12291232
} else if (this.metadata(fromIdx).isFieldUsed(fromField)) {
12301233
result.setUsedOverridden(fromIdx);
12311234
this.hints.push(new CompositionHint(
12321235
HINTS.OVERRIDDEN_FIELD_CAN_BE_REMOVED,
12331236
`Field "${dest.coordinate}" on subgraph "${sourceSubgraphName}" is overridden. It is still used in some federation directive(s) (@key, @requires, and/or @provides) and/or to satisfy interface constraint(s), but consider marking it @external explicitly or removing it along with its references.`,
1237+
dest,
12341238
overriddenSubgraphASTNode,
12351239
));
12361240
} else {
12371241
result.setUnusedOverridden(fromIdx);
12381242
this.hints.push(new CompositionHint(
12391243
HINTS.OVERRIDDEN_FIELD_CAN_BE_REMOVED,
12401244
`Field "${dest.coordinate}" on subgraph "${sourceSubgraphName}" is overridden. Consider removing it.`,
1245+
dest,
12411246
overriddenSubgraphASTNode,
12421247
));
12431248
}
@@ -1969,6 +1974,7 @@ class Merger {
19691974
this.hints.push(new CompositionHint(
19701975
HINTS.UNUSED_ENUM_TYPE,
19711976
`Enum type "${dest}" is defined but unused. It will be included in the supergraph with all the values appearing in any subgraph ("as if" it was only used as an output type).`,
1977+
dest
19721978
));
19731979
}
19741980

@@ -2046,6 +2052,7 @@ class Merger {
20462052
message: `Value "${value}" of enum type "${dest}" will not be part of the supergraph as it is not defined in all the subgraphs defining "${dest}": `,
20472053
supergraphElement: dest,
20482054
subgraphElements: sources,
2055+
targetedElement: value,
20492056
elementToString: (type) => type.value(value.name) ? 'yes' : 'no',
20502057
supergraphElementPrinter: (_, subgraphs) => `"${value}" is defined in ${subgraphs}`,
20512058
otherElementsPrinter: (_, subgraphs) => ` but not in ${subgraphs}`,
@@ -2056,7 +2063,7 @@ class Merger {
20562063
value.remove();
20572064
}
20582065
} else if (position === 'Output') {
2059-
this.hintOnInconsistentOutputEnumValue(sources, dest, value.name);
2066+
this.hintOnInconsistentOutputEnumValue(sources, dest, value);
20602067
}
20612068
}
20622069

@@ -2083,8 +2090,9 @@ class Merger {
20832090
private hintOnInconsistentOutputEnumValue(
20842091
sources: (EnumType | undefined)[],
20852092
dest: EnumType,
2086-
valueName: string
2093+
value: EnumValue,
20872094
) {
2095+
const valueName: string = value.name
20882096
for (const source of sources) {
20892097
// As soon as we find a subgraph that has the type but not the member, we hint.
20902098
if (source && !source.value(valueName)) {
@@ -2093,6 +2101,7 @@ class Merger {
20932101
message: `Value "${valueName}" of enum type "${dest}" has been added to the supergraph but is only defined in a subset of the subgraphs defining "${dest}": `,
20942102
supergraphElement: dest,
20952103
subgraphElements: sources,
2104+
targetedElement: value,
20962105
elementToString: type => type.value(valueName) ? 'yes' : 'no',
20972106
supergraphElementPrinter: (_, subgraphs) => `"${valueName}" is defined in ${subgraphs}`,
20982107
otherElementsPrinter: (_, subgraphs) => ` but not in ${subgraphs}`,
@@ -2491,6 +2500,7 @@ class Merger {
24912500
this.mismatchReporter.pushHint(new CompositionHint(
24922501
HINTS.MERGED_NON_REPEATABLE_DIRECTIVE_ARGUMENTS,
24932502
`Directive @${name} is applied to "${(dest as any)['coordinate'] ?? dest}" in multiple subgraphs with different arguments. Merging strategies used by arguments: ${info.argumentsMerger}`,
2503+
undefined,
24942504
));
24952505
} else {
24962506
const idx = indexOfMax(counts);

composition-js/src/merging/reporter.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { addSubgraphToASTNode, assert, ErrorCodeDefinition, joinStrings, MultiMap, printSubgraphNames, SubgraphASTNode } from '@apollo/federation-internals';
1+
import { addSubgraphToASTNode, assert, ErrorCodeDefinition, joinStrings, MultiMap, NamedSchemaElement, printSubgraphNames, SubgraphASTNode } from '@apollo/federation-internals';
22
import { ASTNode, GraphQLError } from 'graphql';
33
import { CompositionHint, HintCodeDefinition } from '../hints';
44

@@ -101,6 +101,7 @@ export class MismatchReporter {
101101
message,
102102
supergraphElement,
103103
subgraphElements,
104+
targetedElement,
104105
elementToString,
105106
supergraphElementPrinter,
106107
otherElementsPrinter,
@@ -112,6 +113,7 @@ export class MismatchReporter {
112113
message: string,
113114
supergraphElement: TMismatched,
114115
subgraphElements: (TMismatched | undefined)[],
116+
targetedElement?: NamedSchemaElement<any, any, any>
115117
elementToString: (elt: TMismatched, isSupergraph: boolean) => string | undefined,
116118
supergraphElementPrinter: (elt: string, subgraphs: string | undefined) => string,
117119
otherElementsPrinter: (elt: string, subgraphs: string) => string,
@@ -129,6 +131,7 @@ export class MismatchReporter {
129131
this.pushHint(new CompositionHint(
130132
code,
131133
message + distribution[0] + joinStrings(distribution.slice(1), ' and ') + (noEndOfMessageDot ? '' : '.'),
134+
targetedElement ?? ((supergraphElement instanceof NamedSchemaElement) ? supergraphElement as NamedSchemaElement<any, any, any> : undefined),
132135
astNodes
133136
));
134137
},

composition-js/src/validate.ts

+1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ function shareableFieldMismatchedRuntimeTypesHint(
143143
return new CompositionHint(
144144
HINTS.INCONSISTENT_RUNTIME_TYPES_FOR_SHAREABLE_RETURN,
145145
message,
146+
field,
146147
subgraphNodes(state, (s) => (s.type(field.parent.name) as CompositeType | undefined)?.field(field.name)?.sourceAST),
147148
);
148149
}

0 commit comments

Comments
 (0)