UNPKG

@apollo/composition

Version:
199 lines (191 loc) 7.25 kB
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); } }