@apollo/composition
Version:
Apollo Federation composition utilities
199 lines (191 loc) • 7.25 kB
text/typescript
import { addSubgraphToASTNode, assert, ErrorCodeDefinition, joinStrings, MultiMap, NamedSchemaElement, printSubgraphNames, SubgraphASTNode } from '@apollo/federation-internals';
import { ASTNode, GraphQLError } from 'graphql';
import { CompositionHint, HintCodeDefinition } from '../hints';
import { Sources } from './merge';
export class MismatchReporter {
pushError: (error: GraphQLError) => void;
pushHint: (hint: CompositionHint) => void;
constructor(readonly names: readonly string[], pushError: (error: GraphQLError) => void, pushHint: (hint: CompositionHint) => void) {
this.pushError = pushError;
this.pushHint = pushHint;
}
reportMismatchError<TMismatched extends { sourceAST?: ASTNode }>(
code: ErrorCodeDefinition,
message: string,
mismatchedElement:TMismatched,
subgraphElements: Sources<TMismatched>,
mismatchAccessor: (elt: TMismatched, isSupergraph: boolean) => string | undefined
) {
this.reportMismatch(
mismatchedElement,
subgraphElements,
mismatchAccessor,
(elt, names) => `${elt} in ${names}`,
(elt, names) => `${elt} in ${names}`,
(distribution, nodes) => {
this.pushError(code.err(
message + joinStrings(distribution, ' and ', ' but '),
{ nodes }
));
},
elt => !elt
);
}
reportMismatchErrorWithoutSupergraph<TMismatched extends { sourceAST?: ASTNode }>(
code: ErrorCodeDefinition,
message: string,
subgraphElements: Sources<TMismatched>,
mismatchAccessor: (elt: TMismatched, isSupergraph: boolean) => string | undefined
) {
this.reportMismatch(
undefined,
subgraphElements,
mismatchAccessor,
() => '',
(elt, names) => `${elt} in ${names}`,
(distribution, nodes) => {
this.pushError(code.err(
message + joinStrings(distribution, ' and ', ' but '),
{ nodes }
));
},
elt => !elt
);
}
reportMismatchErrorWithSpecifics<TMismatched extends { sourceAST?: ASTNode }>({
code,
message,
mismatchedElement,
subgraphElements,
mismatchAccessor,
supergraphElementPrinter,
otherElementsPrinter,
ignorePredicate,
includeMissingSources = false,
extraNodes,
}: {
code: ErrorCodeDefinition,
message: string,
mismatchedElement: TMismatched,
subgraphElements: Sources<TMismatched>,
mismatchAccessor: (elt: TMismatched | undefined, isSupergraph: boolean) => string | undefined,
supergraphElementPrinter: (elt: string, subgraphs: string | undefined) => string,
otherElementsPrinter: (elt: string, subgraphs: string) => string,
ignorePredicate?: (elt: TMismatched | undefined) => boolean,
includeMissingSources?: boolean,
extraNodes?: SubgraphASTNode[],
}) {
this.reportMismatch(
mismatchedElement,
subgraphElements,
mismatchAccessor,
supergraphElementPrinter,
otherElementsPrinter,
(distribution, nodes) => {
this.pushError(code.err(
message + distribution[0] + joinStrings(distribution.slice(1), ' and '),
{ nodes: nodes.concat(extraNodes ?? []) }
));
},
ignorePredicate,
includeMissingSources
);
}
reportMismatchHint<TMismatched extends { sourceAST?: ASTNode }>({
code,
message,
supergraphElement,
subgraphElements,
targetedElement,
elementToString,
supergraphElementPrinter,
otherElementsPrinter,
ignorePredicate,
includeMissingSources = false,
noEndOfMessageDot = false,
}: {
code: HintCodeDefinition,
message: string,
supergraphElement: TMismatched,
subgraphElements: Sources<TMismatched>,
targetedElement?: NamedSchemaElement<any, any, any>
elementToString: (elt: TMismatched, isSupergraph: boolean) => string | undefined,
supergraphElementPrinter: (elt: string, subgraphs: string | undefined) => string,
otherElementsPrinter: (elt: string, subgraphs: string) => string,
ignorePredicate?: (elt: TMismatched | undefined) => boolean,
includeMissingSources?: boolean,
noEndOfMessageDot?: boolean
}) {
this.reportMismatch(
supergraphElement,
subgraphElements,
elementToString,
supergraphElementPrinter,
otherElementsPrinter,
(distribution, astNodes) => {
this.pushHint(new CompositionHint(
code,
message + distribution[0] + joinStrings(distribution.slice(1), ' and ') + (noEndOfMessageDot ? '' : '.'),
targetedElement ?? ((supergraphElement instanceof NamedSchemaElement) ? supergraphElement as NamedSchemaElement<any, any, any> : undefined),
astNodes
));
},
ignorePredicate,
includeMissingSources
);
}
// Not meant to be used directly: use `reportMismatchError` or `reportMismatchHint` instead.
private reportMismatch<TMismatched extends { sourceAST?: ASTNode }>(
supergraphElement:TMismatched | undefined,
subgraphElements: Sources<TMismatched>,
mismatchAccessor: (element: TMismatched, isSupergraph: boolean) => string | undefined,
supergraphElementPrinter: (elt: string, subgraphs: string | undefined) => string,
otherElementsPrinter: (elt: string, subgraphs: string) => string,
reporter: (distribution: string[], astNode: SubgraphASTNode[]) => void,
ignorePredicate?: (elt: TMismatched | undefined) => boolean,
includeMissingSources: boolean = false
) {
const distributionMap = new MultiMap<string, string>();
const astNodes: SubgraphASTNode[] = [];
const processSubgraphElt = (name: string, subgraphElt: TMismatched) => {
if (ignorePredicate && ignorePredicate(subgraphElt)) {
return;
}
const elt = mismatchAccessor(subgraphElt, false);
distributionMap.add(elt ?? '', name);
if (subgraphElt.sourceAST) {
astNodes.push(addSubgraphToASTNode(subgraphElt.sourceAST, name));
}
}
if (includeMissingSources) {
for (const [i, name] of this.names.entries()) {
const subgraphElt = subgraphElements.get(i);
if (!subgraphElt) {
distributionMap.add('', name);
continue;
}
processSubgraphElt(name, subgraphElt);
}
} else {
for (const [i, subgraphElt] of subgraphElements.entries()) {
if (!subgraphElt) {
continue;
}
processSubgraphElt(this.names[i], subgraphElt);
}
}
const supergraphMismatch = (supergraphElement && mismatchAccessor(supergraphElement, true)) ?? '';
assert(distributionMap.size > 1, () => `Should not have been called for ${supergraphElement}`);
const distribution = [];
// We always add the "supergraph" first (proper formatting of hints rely on this in particular).
const subgraphsLikeSupergraph = distributionMap.get(supergraphMismatch);
distribution.push(supergraphElementPrinter(supergraphMismatch, subgraphsLikeSupergraph ? printSubgraphNames(subgraphsLikeSupergraph) : undefined));
for (const [v, names] of distributionMap.entries()) {
if (v === supergraphMismatch) {
continue;
}
distribution.push(otherElementsPrinter(v, printSubgraphNames(names)));
}
reporter(distribution, astNodes);
}
}