UNPKG

@apollo/composition

Version:
679 lines 35.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ValidationState = exports.ValidationContext = exports.extractValidationError = exports.validateGraphComposition = exports.ValidationError = void 0; const federation_internals_1 = require("@apollo/federation-internals"); const query_graphs_1 = require("@apollo/query-graphs"); const hints_1 = require("./hints"); const graphql_1 = require("graphql"); const debug = (0, federation_internals_1.newDebugLogger)('validation'); class ValidationError extends Error { constructor(message, supergraphUnsatisfiablePath, subgraphsPaths, witness) { super(message); this.supergraphUnsatisfiablePath = supergraphUnsatisfiablePath; this.subgraphsPaths = subgraphsPaths; this.witness = witness; this.name = 'ValidationError'; } } exports.ValidationError = ValidationError; function satisfiabilityError(unsatisfiablePath, subgraphsPaths, subgraphsPathsUnadvanceables) { const witness = buildWitnessOperation(unsatisfiablePath); const operation = (0, graphql_1.print)((0, federation_internals_1.operationToDocument)(witness)); const message = `The following supergraph API query:\n${operation}\n` + 'cannot be satisfied by the subgraphs because:\n' + displayReasons(subgraphsPathsUnadvanceables); const error = new ValidationError(message, unsatisfiablePath, subgraphsPaths, witness); return federation_internals_1.ERRORS.SATISFIABILITY_ERROR.err(error.message, { originalError: error, }); } function subgraphNodes(state, extractNode) { return state.currentSubgraphs().map(({ name, schema }) => { const node = extractNode(schema); return node ? (0, federation_internals_1.addSubgraphToASTNode)(node, name) : undefined; }).filter(federation_internals_1.isDefined); } function shareableFieldNonIntersectingRuntimeTypesError(invalidState, field, runtimeTypesToSubgraphs) { const witness = buildWitnessOperation(invalidState.supergraphPath); const operation = (0, graphql_1.print)((0, federation_internals_1.operationToDocument)(witness)); const typeStrings = [...runtimeTypesToSubgraphs].map(([ts, subgraphs]) => ` - in ${(0, federation_internals_1.printSubgraphNames)(subgraphs)}, ${ts}`); const message = `For the following supergraph API query:\n${operation}` + `\nShared field "${field.coordinate}" return type "${field.type}" has a non-intersecting set of possible runtime types across subgraphs. Runtime types in subgraphs are:` + `\n${typeStrings.join(';\n')}.` + `\nThis is not allowed as shared fields must resolve the same way in all subgraphs, and that imply at least some common runtime types between the subgraphs.`; const error = new ValidationError(message, invalidState.supergraphPath, invalidState.allSubgraphPathInfos().map((p) => p.path.path), witness); return federation_internals_1.ERRORS.SHAREABLE_HAS_MISMATCHED_RUNTIME_TYPES.err(error.message, { nodes: subgraphNodes(invalidState, (s) => { var _a, _b; return (_b = (_a = s.type(field.parent.name)) === null || _a === void 0 ? void 0 : _a.field(field.name)) === null || _b === void 0 ? void 0 : _b.sourceAST; }), }); } function shareableFieldMismatchedRuntimeTypesHint(state, field, commonRuntimeTypes, runtimeTypesPerSubgraphs) { const witness = buildWitnessOperation(state.supergraphPath); const operation = (0, graphql_1.print)((0, federation_internals_1.operationToDocument)(witness)); const allSubgraphs = state.currentSubgraphNames(); const printTypes = (ts) => (0, federation_internals_1.printHumanReadableList)(ts.map((t) => '"' + t + '"'), { prefix: 'type', prefixPlural: 'types' }); const subgraphsWithTypeNotInIntersectionString = allSubgraphs.map((s) => { const typesToNotImplement = runtimeTypesPerSubgraphs.get(s).filter((t) => !commonRuntimeTypes.includes(t)); if (typesToNotImplement.length === 0) { return undefined; } return ` - subgraph "${s}" should never resolve "${field.coordinate}" to an object of ${printTypes(typesToNotImplement)}`; }).filter(federation_internals_1.isDefined); const message = `For the following supergraph API query:\n${operation}` + `\nShared field "${field.coordinate}" return type "${field.type}" has different sets of possible runtime types across subgraphs.` + `\nSince a shared field must be resolved the same way in all subgraphs, make sure that ${(0, federation_internals_1.printSubgraphNames)(allSubgraphs)} only resolve "${field.coordinate}" to objects of ${printTypes(commonRuntimeTypes)}. In particular:` + `\n${subgraphsWithTypeNotInIntersectionString.join(';\n')}.` + `\nOtherwise the @shareable contract will be broken.`; return new hints_1.CompositionHint(hints_1.HINTS.INCONSISTENT_RUNTIME_TYPES_FOR_SHAREABLE_RETURN, message, field, subgraphNodes(state, (s) => { var _a, _b; return (_b = (_a = s.type(field.parent.name)) === null || _a === void 0 ? void 0 : _a.field(field.name)) === null || _b === void 0 ? void 0 : _b.sourceAST; })); } function displayReasons(reasons) { const bySubgraph = new federation_internals_1.MultiMap(); for (const reason of reasons) { for (const unadvanceable of reason.reasons) { bySubgraph.add(unadvanceable.sourceSubgraph, unadvanceable); } } return [...bySubgraph.entries()].map(([subgraph, reasons]) => { let msg = `- from subgraph "${subgraph}":`; if (reasons.length === 1) { msg += ' ' + reasons[0].details + '.'; } else { const allDetails = new Set(reasons.map((r) => r.details)); for (const details of allDetails) { msg += '\n - ' + details + '.'; } } return msg; }).join('\n'); } function buildWitnessOperation(witness) { (0, federation_internals_1.assert)(witness.size > 0, "unsatisfiablePath should contain at least one edge/transition"); const root = witness.root; const schema = witness.graph.sources.get(root.source); return new federation_internals_1.Operation(schema, root.rootKind, buildWitnessNextStep([...witness].map(e => e[0]), 0), new federation_internals_1.VariableDefinitions()); } function buildWitnessNextStep(edges, index) { if (index >= edges.length) { const lastType = edges[edges.length - 1].tail.type; (0, federation_internals_1.assert)((0, federation_internals_1.isOutputType)(lastType), 'Should not have input types as vertex types'); return (0, federation_internals_1.isLeafType)(lastType) ? undefined : new federation_internals_1.SelectionSet(lastType); } const edge = edges[index]; let selection; const subSelection = buildWitnessNextStep(edges, index + 1); switch (edge.transition.kind) { case 'DownCast': const type = edge.transition.castedType; selection = (0, federation_internals_1.selectionOfElement)(new federation_internals_1.FragmentElement(edge.transition.sourceType, type.name), subSelection); break; case 'FieldCollection': const field = edge.transition.definition; selection = new federation_internals_1.FieldSelection(buildWitnessField(field), subSelection); break; case 'SubgraphEnteringTransition': case 'KeyResolution': case 'RootTypeResolution': case 'InterfaceObjectFakeDownCast': (0, federation_internals_1.assert)(false, `Invalid edge ${edge} found in supergraph path`); } return (0, federation_internals_1.selectionSetOf)(edge.head.type, selection); } function buildWitnessField(definition) { if (definition.arguments().length === 0) { return new federation_internals_1.Field(definition); } const args = Object.create(null); for (const argDef of definition.arguments()) { args[argDef.name] = generateWitnessValue(argDef.type); } return new federation_internals_1.Field(definition, args); } function generateWitnessValue(type) { switch (type.kind) { case 'ScalarType': switch (type.name) { case 'Int': return 0; case 'Float': return 3.14; case 'Boolean': return true; case 'String': return 'A string value'; case 'ID': return '<any id>'; default: return '<some value>'; } case 'EnumType': return type.values[0].name; case 'InputObjectType': const obj = Object.create(null); for (const field of type.fields()) { if (field.defaultValue || (0, federation_internals_1.isNullableType)(field.type)) { continue; } obj[field.name] = generateWitnessValue(field.type); } return obj; case 'ListType': return []; case 'NonNullType': return generateWitnessValue(type.ofType); default: (0, federation_internals_1.assert)(false, `Unhandled input type ${type}`); } } function validateGraphComposition(supergraphSchema, subgraphNameToGraphEnumValue, supergraphAPI, federatedQueryGraph, compositionOptions = {}) { const { errors, hints } = new ValidationTraversal(supergraphSchema, subgraphNameToGraphEnumValue, supergraphAPI, federatedQueryGraph, compositionOptions).validate(); return errors.length > 0 ? { errors, hints } : { hints }; } exports.validateGraphComposition = validateGraphComposition; function initialSubgraphPaths(kind, subgraphs) { const root = subgraphs.root(kind); (0, federation_internals_1.assert)(root, () => `The supergraph shouldn't have a ${kind} root if no subgraphs have one`); (0, federation_internals_1.assert)(root.type.name == (0, query_graphs_1.federatedGraphRootTypeName)(kind), () => `Unexpected type ${root.type} for subgraphs root type (expected ${(0, query_graphs_1.federatedGraphRootTypeName)(kind)}`); const initialState = query_graphs_1.GraphPath.fromGraphRoot(subgraphs, kind); return subgraphs.outEdges(root).map(e => initialState.add(query_graphs_1.subgraphEnteringTransition, e, query_graphs_1.noConditionsResolution)); } function possibleRuntimeTypeNamesSorted(path) { const types = path.tailPossibleRuntimeTypes().map((o) => o.name); types.sort((a, b) => a.localeCompare(b)); return types; } function extractValidationError(error) { if (!(error instanceof graphql_1.GraphQLError) || !(error.originalError instanceof ValidationError)) { return undefined; } return error.originalError; } exports.extractValidationError = extractValidationError; class ValidationContext { constructor(supergraphSchema, subgraphNameToGraphEnumValue) { var _a, _b; this.supergraphSchema = supergraphSchema; this.subgraphNameToGraphEnumValue = subgraphNameToGraphEnumValue; const [_, joinSpec] = (0, federation_internals_1.validateSupergraph)(supergraphSchema); this.joinTypeDirective = joinSpec.typeDirective(supergraphSchema); this.joinFieldDirective = joinSpec.fieldDirective(supergraphSchema); this.typesToContexts = new Map(); let contextDirective; const contextFeature = (_a = supergraphSchema.coreFeatures) === null || _a === void 0 ? void 0 : _a.getByIdentity(federation_internals_1.ContextSpecDefinition.identity); if (contextFeature) { const contextSpec = federation_internals_1.CONTEXT_VERSIONS.find(contextFeature.url.version); (0, federation_internals_1.assert)(contextSpec, `Unexpected context spec version ${contextFeature.url.version}`); contextDirective = contextSpec.contextDirective(supergraphSchema); } for (const application of (_b = contextDirective === null || contextDirective === void 0 ? void 0 : contextDirective.applications()) !== null && _b !== void 0 ? _b : []) { const { name: context } = application.arguments(); (0, federation_internals_1.assert)(application.parent instanceof federation_internals_1.NamedSchemaElement, "Unexpectedly found unnamed element with @context"); const type = supergraphSchema.type(application.parent.name); (0, federation_internals_1.assert)(type, `Type ${application.parent.name} unexpectedly doesn't exist`); const typeNames = [type.name]; if ((0, federation_internals_1.isInterfaceType)(type)) { typeNames.push(...type.allImplementations().map((t) => t.name)); } else if ((0, federation_internals_1.isUnionType)(type)) { typeNames.push(...type.types().map((t) => t.name)); } for (const typeName of typeNames) { if (this.typesToContexts.has(typeName)) { this.typesToContexts.get(typeName).add(context); } else { this.typesToContexts.set(typeName, new Set([context])); } } } } isShareable(field) { const typeInSupergraph = this.supergraphSchema.type(field.parent.name); (0, federation_internals_1.assert)(typeInSupergraph && (0, federation_internals_1.isCompositeType)(typeInSupergraph), () => `${field.parent.name} should exists in the supergraph and be a composite`); if (!(0, federation_internals_1.isObjectType)(typeInSupergraph)) { return false; } const fieldInSupergraph = typeInSupergraph.field(field.name); (0, federation_internals_1.assert)(fieldInSupergraph, () => `${field.coordinate} should exists in the supergraph`); const joinFieldApplications = fieldInSupergraph.appliedDirectivesOf(this.joinFieldDirective); return joinFieldApplications.length === 0 ? typeInSupergraph.appliedDirectivesOf(this.joinTypeDirective).length > 1 : (joinFieldApplications.filter((application) => { const args = application.arguments(); return !args.external && !args.usedOverridden; }).length > 1); } matchingContexts(typeName) { var _a; return [...((_a = this.typesToContexts.get(typeName)) !== null && _a !== void 0 ? _a : [])]; } } exports.ValidationContext = ValidationContext; class SubgraphPathInfos { constructor(paths) { this.paths = paths; } } class TopLevelMutationFieldSubgraphPathInfos { constructor(mutationField, paths) { this.mutationField = mutationField; this.paths = paths; } } class ValidationState { constructor(supergraphPath, subgraphPathInfos, selectedOverrideConditions = new Map()) { this.supergraphPath = supergraphPath; this.subgraphPathInfos = subgraphPathInfos; this.selectedOverrideConditions = selectedOverrideConditions; } static initial({ supergraphAPI, kind, federatedQueryGraph, conditionResolver, overrideConditions, }) { return new ValidationState(query_graphs_1.GraphPath.fromGraphRoot(supergraphAPI, kind), new SubgraphPathInfos(initialSubgraphPaths(kind, federatedQueryGraph).map((p) => query_graphs_1.TransitionPathWithLazyIndirectPaths.initial(p, conditionResolver, overrideConditions)).map((p) => ({ path: p, contexts: new Map(), })))); } canSkipVisit(subgraphNameToGraphEnumValue, previousVisits) { const vertex = this.supergraphPath.tail; if (this.subgraphPathInfos instanceof SubgraphPathInfos) { return this.canSkipVisitForSubgraphPaths(vertex, this.subgraphPathInfos.paths, subgraphNameToGraphEnumValue, previousVisits); } else { let canSkip = true; for (const subgraphPathInfos of this.subgraphPathInfos.paths.values()) { if (!this.canSkipVisitForSubgraphPaths(vertex, subgraphPathInfos, subgraphNameToGraphEnumValue, previousVisits)) { canSkip = false; } } return canSkip; } } canSkipVisitForSubgraphPaths(supergraphPathTail, subgraphPathInfos, subgraphNameToGraphEnumValue, previousVisits) { const currentVertexVisit = { subgraphContextKeys: this.currentSubgraphContextKeys(subgraphNameToGraphEnumValue, subgraphPathInfos), overrideConditions: this.selectedOverrideConditions }; const previousVisitsForVertex = previousVisits.getVertexState(supergraphPathTail); if (previousVisitsForVertex) { for (const previousVisit of previousVisitsForVertex) { if (isSupersetOrEqual(currentVertexVisit, previousVisit)) { debug.groupEnd(`Has already validated this vertex.`); return true; } } previousVisitsForVertex.push(currentVertexVisit); } else { previousVisits.setVertexState(supergraphPathTail, [currentVertexVisit]); } return false; } validateTransition(context, supergraphEdge, matchingContexts, validationErrors, satisfiabilityErrorsByMutationFieldAndSubgraph) { (0, federation_internals_1.assert)(!supergraphEdge.conditions, () => `Supergraph edges should not have conditions (${supergraphEdge})`); const transition = supergraphEdge.transition; const targetType = supergraphEdge.tail.type; const newOverrideConditions = new Map([...this.selectedOverrideConditions]); if (supergraphEdge.overrideCondition) { newOverrideConditions.set(supergraphEdge.overrideCondition.label, supergraphEdge.overrideCondition.condition); } const newPath = this.supergraphPath.add(transition, supergraphEdge, query_graphs_1.noConditionsResolution); let updatedState; if (this.subgraphPathInfos instanceof SubgraphPathInfos) { const { newSubgraphPathInfos, error, } = this.validateTransitionForSubgraphPaths(this.subgraphPathInfos.paths, newOverrideConditions, transition, targetType, matchingContexts, newPath); if (error) { validationErrors.push(error); return {}; } if (newSubgraphPathInfos.length === 0) { return {}; } const mutationField = ValidationState.fieldIfTopLevelMutation(this.supergraphPath, supergraphEdge); if (mutationField) { const partitionedSubgraphPathInfos = new Map(); for (const subgraphPathInfo of newSubgraphPathInfos) { let subgraph = ValidationState.subgraphOfTopLevelMutation(subgraphPathInfo); let subgraphPathInfos = partitionedSubgraphPathInfos.get(subgraph); if (!subgraphPathInfos) { subgraphPathInfos = []; partitionedSubgraphPathInfos.set(subgraph, subgraphPathInfos); } subgraphPathInfos.push(subgraphPathInfo); } if (partitionedSubgraphPathInfos.size <= 1) { updatedState = new ValidationState(newPath, new SubgraphPathInfos([...partitionedSubgraphPathInfos.values()].flat()), newOverrideConditions); } else { let errorsBySubgraph = satisfiabilityErrorsByMutationFieldAndSubgraph .get(mutationField.coordinate); if (!errorsBySubgraph) { errorsBySubgraph = new Map(); satisfiabilityErrorsByMutationFieldAndSubgraph.set(mutationField.coordinate, errorsBySubgraph); } for (const subgraph of partitionedSubgraphPathInfos.keys()) { if (!errorsBySubgraph.has(subgraph)) { errorsBySubgraph.set(subgraph, []); } } updatedState = new ValidationState(newPath, { mutationField, paths: partitionedSubgraphPathInfos, }, newOverrideConditions); } } else { updatedState = new ValidationState(newPath, new SubgraphPathInfos(newSubgraphPathInfos), newOverrideConditions); } } else { const partitionedSubgraphPathInfos = new Map(); for (const [subgraph, subgraphPathInfos] of this.subgraphPathInfos.paths) { const errors = satisfiabilityErrorsByMutationFieldAndSubgraph .get(this.subgraphPathInfos.mutationField.coordinate) .get(subgraph); const { newSubgraphPathInfos, error, } = this.validateTransitionForSubgraphPaths(subgraphPathInfos, newOverrideConditions, transition, targetType, matchingContexts, newPath); if (error) { errors.push(error); continue; } if (newSubgraphPathInfos.length === 0) { return {}; } partitionedSubgraphPathInfos.set(subgraph, newSubgraphPathInfos); } if (partitionedSubgraphPathInfos.size === 0) { return {}; } updatedState = new ValidationState(newPath, { mutationField: this.subgraphPathInfos.mutationField, paths: partitionedSubgraphPathInfos, }, newOverrideConditions); } let allSubgraphPathInfos = updatedState.allSubgraphPathInfos(); let hint = undefined; if (allSubgraphPathInfos.length > 1 && transition.kind === 'FieldCollection' && (0, federation_internals_1.isAbstractType)(newPath.tail.type) && context.isShareable(transition.definition)) { const filteredPaths = allSubgraphPathInfos.map((p) => p.path.path).filter((p) => (0, federation_internals_1.isAbstractType)(p.tail.type)); if (filteredPaths.length > 1) { const allRuntimeTypes = possibleRuntimeTypeNamesSorted(newPath); let intersection = allRuntimeTypes; const runtimeTypesToSubgraphs = new federation_internals_1.MultiMap(); const runtimeTypesPerSubgraphs = new federation_internals_1.MultiMap(); let hasAllEmpty = true; for (const { path } of allSubgraphPathInfos) { const subgraph = path.path.tail.source; const typeNames = possibleRuntimeTypeNamesSorted(path.path); if (typeNames.length === 1 && !allRuntimeTypes.includes(typeNames[0])) { continue; } runtimeTypesPerSubgraphs.set(subgraph, typeNames); let typeNamesStr = 'no runtime type is defined'; if (typeNames.length > 0) { typeNamesStr = (typeNames.length > 1 ? 'types ' : 'type ') + (0, federation_internals_1.joinStrings)(typeNames.map((n) => `"${n}"`)); hasAllEmpty = false; } runtimeTypesToSubgraphs.add(typeNamesStr, subgraph); intersection = intersection.filter((t) => typeNames.includes(t)); } if (!hasAllEmpty) { if (intersection.length === 0) { validationErrors.push(shareableFieldNonIntersectingRuntimeTypesError(updatedState, transition.definition, runtimeTypesToSubgraphs)); return {}; } if (runtimeTypesToSubgraphs.size > 1) { hint = shareableFieldMismatchedRuntimeTypesHint(updatedState, transition.definition, intersection, runtimeTypesPerSubgraphs); } } } } return { state: updatedState, hint }; } validateTransitionForSubgraphPaths(subgraphPathInfos, newOverrideConditions, transition, targetType, matchingContexts, newPath) { const newSubgraphPathInfos = []; const deadEnds = []; for (const { path, contexts } of subgraphPathInfos) { const options = (0, query_graphs_1.advancePathWithTransition)(path, transition, targetType, newOverrideConditions); if ((0, query_graphs_1.isUnadvanceableClosures)(options)) { deadEnds.push(options); continue; } if (options.length === 0) { return { newSubgraphPathInfos: [] }; } let newContexts = contexts; if (matchingContexts.length) { const subgraphName = path.path.tail.source; const typeName = path.path.tail.type.name; newContexts = new Map([...contexts]); for (const matchingContext in matchingContexts) { newContexts.set(matchingContext, { subgraphName, typeName, }); } } newSubgraphPathInfos.push(...options.map((p) => ({ path: p, contexts: newContexts }))); } return newSubgraphPathInfos.length === 0 ? { error: satisfiabilityError(newPath, subgraphPathInfos.map((p) => p.path.path), deadEnds.map((d) => d.toUnadvanceables())) } : { newSubgraphPathInfos }; } static fieldIfTopLevelMutation(supergraphPath, edge) { if (supergraphPath.size !== 0) { return null; } if (edge.transition.kind !== 'FieldCollection') { return null; } if (supergraphPath.root !== supergraphPath.graph.root('mutation')) { return null; } return edge.transition.definition; } static subgraphOfTopLevelMutation(subgraphPathInfo) { const lastEdge = subgraphPathInfo.path.path.lastEdge(); (0, federation_internals_1.assert)(lastEdge, "Path unexpectedly missing edge"); return lastEdge.head.source; } allSubgraphPathInfos() { return this.subgraphPathInfos instanceof SubgraphPathInfos ? this.subgraphPathInfos.paths : Array.from(this.subgraphPathInfos.paths.values()).flat(); } allSubgraphPathsCount() { if (this.subgraphPathInfos instanceof SubgraphPathInfos) { return this.subgraphPathInfos.paths.length; } else { let count = 0; for (const subgraphPathInfos of this.subgraphPathInfos.paths.values()) { count += subgraphPathInfos.length; } return count; } } currentSubgraphNames() { const subgraphs = []; for (const pathInfo of this.allSubgraphPathInfos()) { const source = pathInfo.path.path.tail.source; if (!subgraphs.includes(source)) { subgraphs.push(source); } } return subgraphs; } currentSubgraphContextKeys(subgraphNameToGraphEnumValue, subgraphPathInfos) { const subgraphContextKeys = new Set(); for (const pathInfo of subgraphPathInfos) { const tailSubgraphName = pathInfo.path.path.tail.source; const tailSubgraphEnumValue = subgraphNameToGraphEnumValue.get(tailSubgraphName); const tailTypeName = pathInfo.path.path.tail.type.name; const entryKeys = []; const contexts = Array.from(pathInfo.contexts.entries()); contexts.sort((a, b) => a[0].localeCompare(b[0])); for (const [context, { subgraphName, typeName }] of contexts) { const subgraphEnumValue = subgraphNameToGraphEnumValue.get(subgraphName); entryKeys.push(`${context}=${subgraphEnumValue}.${typeName}`); } subgraphContextKeys.add(`${tailSubgraphEnumValue}.${tailTypeName}[${entryKeys.join(',')}]`); } return subgraphContextKeys; } currentSubgraphs() { const allSubgraphPathInfos = this.allSubgraphPathInfos(); if (allSubgraphPathInfos.length === 0) { return []; } const sources = allSubgraphPathInfos[0].path.path.graph.sources; return this.currentSubgraphNames().map((name) => ({ name, schema: sources.get(name) })); } toString() { if (this.subgraphPathInfos instanceof SubgraphPathInfos) { return `${this.supergraphPath} <=> [${this.subgraphPathInfos.paths.map(p => p.path.toString()).join(', ')}]`; } else { return `${this.supergraphPath} <=> {${Array.from(this.subgraphPathInfos.paths.entries()).map(([s, p]) => `${s}: [${p.map(p => p.path.toString()).join(', ')}]`).join(', ')}}`; } } } exports.ValidationState = ValidationState; function isSupersetOrEqual(maybeSuperset, other) { const includesAllSubgraphs = [...other.subgraphContextKeys] .every((s) => maybeSuperset.subgraphContextKeys.has(s)); const includesAllOverrideConditions = [...other.overrideConditions.entries()].every(([label, value]) => maybeSuperset.overrideConditions.get(label) === value); return includesAllSubgraphs && includesAllOverrideConditions; } class ValidationTraversal { constructor(supergraphSchema, subgraphNameToGraphEnumValue, supergraphAPI, federatedQueryGraph, compositionOptions) { var _a; this.stack = []; this.validationErrors = []; this.validationHints = []; this.satisfiabilityErrorsByMutationFieldAndSubgraph = new Map(); this.totalValidationSubgraphPaths = 0; this.maxValidationSubgraphPaths = (_a = compositionOptions.maxValidationSubgraphPaths) !== null && _a !== void 0 ? _a : ValidationTraversal.DEFAULT_MAX_VALIDATION_SUBGRAPH_PATHS; this.conditionResolver = (0, query_graphs_1.simpleValidationConditionResolver)({ supergraph: supergraphSchema, queryGraph: federatedQueryGraph, withCaching: true, }); supergraphAPI.rootKinds().forEach((kind) => this.pushStack(ValidationState.initial({ supergraphAPI, kind, federatedQueryGraph, conditionResolver: this.conditionResolver, overrideConditions: new Map(), }))); this.previousVisits = new query_graphs_1.QueryGraphState(); this.context = new ValidationContext(supergraphSchema, subgraphNameToGraphEnumValue); } pushStack(state) { this.totalValidationSubgraphPaths += state.allSubgraphPathsCount(); this.stack.push(state); if (this.totalValidationSubgraphPaths > this.maxValidationSubgraphPaths) { return { error: federation_internals_1.ERRORS.MAX_VALIDATION_SUBGRAPH_PATHS_EXCEEDED.err(`Maximum number of validation subgraph paths exceeded: ${this.totalValidationSubgraphPaths}`) }; } return {}; } popStack() { const state = this.stack.pop(); if (state) { this.totalValidationSubgraphPaths -= state.allSubgraphPathsCount(); } return state; } validate() { while (this.stack.length > 0) { const { error } = this.handleState(this.popStack()); if (error) { return { errors: [error], hints: this.validationHints }; } } for (const [fieldCoordinate, errorsBySubgraph,] of this.satisfiabilityErrorsByMutationFieldAndSubgraph) { let someSubgraphHasNoErrors = false; for (const errors of errorsBySubgraph.values()) { if (errors.length === 0) { someSubgraphHasNoErrors = true; break; } } if (someSubgraphHasNoErrors) { continue; } let messageParts = [ `Supergraph API queries using the mutation field "${fieldCoordinate}"` + ` at top-level must be satisfiable without needing to call that` + ` field from multiple subgraphs, but every subgraph with that field` + ` encounters satisfiability errors. Please fix these satisfiability` + ` errors for (at least) one of the following subgraphs with the` + ` mutation field:` ]; for (const [subgraph, errors] of errorsBySubgraph) { messageParts.push(`- When calling "${fieldCoordinate}" at top-level from subgraph` + ` "${subgraph}":`); for (const error of errors) { for (const line of error.message.split("\n")) { if (line.length === 0) { messageParts.push(line); } else { messageParts.push(" " + line); } } } } this.validationErrors.push(federation_internals_1.ERRORS.SATISFIABILITY_ERROR.err(messageParts.join("\n"))); } return { errors: this.validationErrors, hints: this.validationHints }; } handleState(state) { var _a, _b, _c; debug.group(() => `Validation: ${this.stack.length + 1} open states. Validating ${state}`); if (state.canSkipVisit(this.context.subgraphNameToGraphEnumValue, this.previousVisits)) { return {}; } for (const edge of state.supergraphPath.nextEdges()) { if (edge.isEdgeForField(federation_internals_1.typenameFieldName)) { continue; } if (edge.overrideCondition && state.selectedOverrideConditions.has(edge.overrideCondition.label) && !edge.satisfiesOverrideConditions(state.selectedOverrideConditions)) { debug.groupEnd(`Edge ${edge} doesn't satisfy label condition: ${(_a = edge.overrideCondition) === null || _a === void 0 ? void 0 : _a.label}(${state.selectedOverrideConditions.get((_c = (_b = edge.overrideCondition) === null || _b === void 0 ? void 0 : _b.label) !== null && _c !== void 0 ? _c : "")}), no need to validate further`); continue; } const matchingContexts = edge.transition.kind === 'FieldCollection' ? this.context.matchingContexts(edge.head.type.name) : []; debug.group(() => `Validating supergraph edge ${edge}`); const { state: newState, hint } = state.validateTransition(this.context, edge, matchingContexts, this.validationErrors, this.satisfiabilityErrorsByMutationFieldAndSubgraph); if (!newState) { debug.groupEnd(`Validation error!`); continue; } if (hint) { this.validationHints.push(hint); } if (newState && !newState.supergraphPath.isTerminal()) { const { error } = this.pushStack(newState); if (error) { return { error }; } debug.groupEnd(() => `Reached new state ${newState}`); } else { debug.groupEnd(`Reached terminal vertex/cycle`); } } debug.groupEnd(); return {}; } } ValidationTraversal.DEFAULT_MAX_VALIDATION_SUBGRAPH_PATHS = 1000000; //# sourceMappingURL=validate.js.map