@apollo/composition
Advanced tools
Comparing version 2.8.0-connectors.5 to 2.8.0
@@ -31,3 +31,3 @@ "use strict"; | ||
const federatedQueryGraph = (0, query_graphs_1.buildFederatedQueryGraph)(supergraph, false); | ||
const { errors, hints } = (0, validate_1.validateGraphComposition)(supergraph.schema, supergraphQueryGraph, federatedQueryGraph); | ||
const { errors, hints } = (0, validate_1.validateGraphComposition)(supergraph.schema, supergraph.subgraphNameToGraphEnumValue(), supergraphQueryGraph, federatedQueryGraph); | ||
if (errors) { | ||
@@ -34,0 +34,0 @@ return { errors }; |
@@ -27,2 +27,3 @@ "use strict"; | ||
'https://specs.apollo.dev/source', | ||
'https://specs.apollo.dev/context', | ||
]; | ||
@@ -103,2 +104,3 @@ class ComposeDirectiveManager { | ||
sg.metadata().policyDirective(), | ||
sg.metadata().contextDirective(), | ||
].map(d => d.name); | ||
@@ -105,0 +107,0 @@ if (directivesComposedByDefault.includes(directive.name)) { |
@@ -45,2 +45,3 @@ import { NamedSchemaElement, SubgraphASTNode } from "@apollo/federation-internals"; | ||
IMPLICITLY_UPGRADED_FEDERATION_VERSION: HintCodeDefinition; | ||
CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS: HintCodeDefinition; | ||
}; | ||
@@ -47,0 +48,0 @@ export declare class CompositionHint { |
@@ -169,2 +169,7 @@ "use strict"; | ||
}); | ||
const CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS = makeCodeDefinition({ | ||
code: 'CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS', | ||
level: HintLevel.INFO, | ||
description: 'Indicates that the argument will not be present in the supergraph because it is contextual in at least one subgraph.' | ||
}); | ||
exports.HINTS = { | ||
@@ -200,2 +205,3 @@ INCONSISTENT_BUT_COMPATIBLE_FIELD_TYPE, | ||
IMPLICITLY_UPGRADED_FEDERATION_VERSION, | ||
CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS, | ||
}; | ||
@@ -202,0 +208,0 @@ class CompositionHint { |
@@ -11,24 +11,29 @@ import { CompositeType, FieldDefinition, Operation, Schema, SchemaRootKind } from "@apollo/federation-internals"; | ||
} | ||
export declare function validateGraphComposition(supergraphSchema: Schema, supergraphAPI: QueryGraph, federatedQueryGraph: QueryGraph): { | ||
export declare function validateGraphComposition(supergraphSchema: Schema, subgraphNameToGraphEnumValue: Map<string, string>, supergraphAPI: QueryGraph, federatedQueryGraph: QueryGraph): { | ||
errors?: GraphQLError[]; | ||
hints?: CompositionHint[]; | ||
}; | ||
export declare function computeSubgraphPaths(supergraphSchema: Schema, supergraphPath: RootPath<Transition>, federatedQueryGraph: QueryGraph, overrideConditions: Map<string, boolean>): { | ||
traversal?: ValidationState; | ||
isComplete?: boolean; | ||
error?: GraphQLError; | ||
}; | ||
export declare function extractValidationError(error: any): ValidationError | undefined; | ||
export declare class ValidationContext { | ||
readonly supergraphSchema: Schema; | ||
readonly subgraphNameToGraphEnumValue: Map<string, string>; | ||
private readonly joinTypeDirective; | ||
private readonly joinFieldDirective; | ||
constructor(supergraphSchema: Schema); | ||
private readonly typesToContexts; | ||
constructor(supergraphSchema: Schema, subgraphNameToGraphEnumValue: Map<string, string>); | ||
isShareable(field: FieldDefinition<CompositeType>): boolean; | ||
matchingContexts(typeName: string): string[]; | ||
} | ||
type SubgraphPathInfo = { | ||
path: TransitionPathWithLazyIndirectPaths<RootVertex>; | ||
contexts: Map<string, { | ||
subgraphName: string; | ||
typeName: string; | ||
}>; | ||
}; | ||
export declare class ValidationState { | ||
readonly supergraphPath: RootPath<Transition>; | ||
readonly subgraphPaths: TransitionPathWithLazyIndirectPaths<RootVertex>[]; | ||
readonly subgraphPathInfos: SubgraphPathInfo[]; | ||
selectedOverrideConditions: Map<string, boolean>; | ||
constructor(supergraphPath: RootPath<Transition>, subgraphPaths: TransitionPathWithLazyIndirectPaths<RootVertex>[], selectedOverrideConditions?: Map<string, boolean>); | ||
constructor(supergraphPath: RootPath<Transition>, subgraphPathInfos: SubgraphPathInfo[], selectedOverrideConditions?: Map<string, boolean>); | ||
static initial({ supergraphAPI, kind, federatedQueryGraph, conditionResolver, overrideConditions, }: { | ||
@@ -41,3 +46,3 @@ supergraphAPI: QueryGraph; | ||
}): ValidationState; | ||
validateTransition(context: ValidationContext, supergraphEdge: Edge): { | ||
validateTransition(context: ValidationContext, supergraphEdge: Edge, matchingContexts: string[]): { | ||
state?: ValidationState; | ||
@@ -48,2 +53,3 @@ error?: GraphQLError; | ||
currentSubgraphNames(): string[]; | ||
currentSubgraphContextKeys(subgraphNameToGraphEnumValue: Map<string, string>): Set<string>; | ||
currentSubgraphs(): { | ||
@@ -55,2 +61,3 @@ name: string; | ||
} | ||
export {}; | ||
//# sourceMappingURL=validate.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.ValidationState = exports.ValidationContext = exports.extractValidationError = exports.computeSubgraphPaths = exports.validateGraphComposition = exports.ValidationError = void 0; | ||
exports.ValidationState = exports.ValidationContext = exports.extractValidationError = exports.validateGraphComposition = exports.ValidationError = void 0; | ||
const federation_internals_1 = require("@apollo/federation-internals"); | ||
@@ -44,3 +44,3 @@ const query_graphs_1 = require("@apollo/query-graphs"); | ||
+ `\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.subgraphPaths.map((p) => p.path), witness); | ||
const error = new ValidationError(message, invalidState.supergraphPath, invalidState.subgraphPathInfos.map((p) => p.path.path), witness); | ||
return federation_internals_1.ERRORS.SHAREABLE_HAS_MISMATCHED_RUNTIME_TYPES.err(error.message, { | ||
@@ -171,36 +171,7 @@ 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 validateGraphComposition(supergraphSchema, supergraphAPI, federatedQueryGraph) { | ||
const { errors, hints } = new ValidationTraversal(supergraphSchema, supergraphAPI, federatedQueryGraph).validate(); | ||
function validateGraphComposition(supergraphSchema, subgraphNameToGraphEnumValue, supergraphAPI, federatedQueryGraph) { | ||
const { errors, hints } = new ValidationTraversal(supergraphSchema, subgraphNameToGraphEnumValue, supergraphAPI, federatedQueryGraph).validate(); | ||
return errors.length > 0 ? { errors, hints } : { hints }; | ||
} | ||
exports.validateGraphComposition = validateGraphComposition; | ||
function computeSubgraphPaths(supergraphSchema, supergraphPath, federatedQueryGraph, overrideConditions) { | ||
try { | ||
(0, federation_internals_1.assert)(!supergraphPath.hasAnyEdgeConditions(), () => `A supergraph path should not have edge condition paths (as supergraph edges should not have conditions): ${supergraphPath}`); | ||
const conditionResolver = (0, query_graphs_1.simpleValidationConditionResolver)({ supergraph: supergraphSchema, queryGraph: federatedQueryGraph, withCaching: true }); | ||
const initialState = ValidationState.initial({ supergraphAPI: supergraphPath.graph, kind: supergraphPath.root.rootKind, federatedQueryGraph, conditionResolver, overrideConditions }); | ||
const context = new ValidationContext(supergraphSchema); | ||
let state = initialState; | ||
let isIncomplete = false; | ||
for (const [edge] of supergraphPath) { | ||
const { state: updated, error } = state.validateTransition(context, edge); | ||
if (error) { | ||
throw error; | ||
} | ||
if (!updated) { | ||
isIncomplete = true; | ||
break; | ||
} | ||
state = updated; | ||
} | ||
return { traversal: state, isComplete: !isIncomplete }; | ||
} | ||
catch (error) { | ||
if (error instanceof graphql_1.GraphQLError) { | ||
return { error }; | ||
} | ||
throw error; | ||
} | ||
} | ||
exports.computeSubgraphPaths = computeSubgraphPaths; | ||
function initialSubgraphPaths(kind, subgraphs) { | ||
@@ -226,7 +197,38 @@ const root = subgraphs.root(kind); | ||
class ValidationContext { | ||
constructor(supergraphSchema) { | ||
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])); | ||
} | ||
} | ||
} | ||
} | ||
@@ -249,18 +251,25 @@ isShareable(field) { | ||
} | ||
matchingContexts(typeName) { | ||
var _a; | ||
return [...((_a = this.typesToContexts.get(typeName)) !== null && _a !== void 0 ? _a : [])]; | ||
} | ||
} | ||
exports.ValidationContext = ValidationContext; | ||
class ValidationState { | ||
constructor(supergraphPath, subgraphPaths, selectedOverrideConditions = new Map()) { | ||
constructor(supergraphPath, subgraphPathInfos, selectedOverrideConditions = new Map()) { | ||
this.supergraphPath = supergraphPath; | ||
this.subgraphPaths = subgraphPaths; | ||
this.subgraphPathInfos = subgraphPathInfos; | ||
this.selectedOverrideConditions = selectedOverrideConditions; | ||
} | ||
static initial({ supergraphAPI, kind, federatedQueryGraph, conditionResolver, overrideConditions, }) { | ||
return new ValidationState(query_graphs_1.GraphPath.fromGraphRoot(supergraphAPI, kind), initialSubgraphPaths(kind, federatedQueryGraph).map((p) => query_graphs_1.TransitionPathWithLazyIndirectPaths.initial(p, conditionResolver, overrideConditions))); | ||
return new ValidationState(query_graphs_1.GraphPath.fromGraphRoot(supergraphAPI, kind), initialSubgraphPaths(kind, federatedQueryGraph).map((p) => query_graphs_1.TransitionPathWithLazyIndirectPaths.initial(p, conditionResolver, overrideConditions)).map((p) => ({ | ||
path: p, | ||
contexts: new Map(), | ||
}))); | ||
} | ||
validateTransition(context, supergraphEdge) { | ||
validateTransition(context, supergraphEdge, matchingContexts) { | ||
(0, federation_internals_1.assert)(!supergraphEdge.conditions, () => `Supergraph edges should not have conditions (${supergraphEdge})`); | ||
const transition = supergraphEdge.transition; | ||
const targetType = supergraphEdge.tail.type; | ||
const newSubgraphPaths = []; | ||
const newSubgraphPathInfos = []; | ||
const deadEnds = []; | ||
@@ -271,3 +280,3 @@ const newOverrideConditions = new Map([...this.selectedOverrideConditions]); | ||
} | ||
for (const path of this.subgraphPaths) { | ||
for (const { path, contexts } of this.subgraphPathInfos) { | ||
const options = (0, query_graphs_1.advancePathWithTransition)(path, transition, targetType, newOverrideConditions); | ||
@@ -281,15 +290,27 @@ if ((0, query_graphs_1.isUnadvanceable)(options)) { | ||
} | ||
newSubgraphPaths.push(...options); | ||
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 }))); | ||
} | ||
const newPath = this.supergraphPath.add(transition, supergraphEdge, query_graphs_1.noConditionsResolution); | ||
if (newSubgraphPaths.length === 0) { | ||
return { error: satisfiabilityError(newPath, this.subgraphPaths.map((p) => p.path), deadEnds) }; | ||
if (newSubgraphPathInfos.length === 0) { | ||
return { error: satisfiabilityError(newPath, this.subgraphPathInfos.map((p) => p.path.path), deadEnds) }; | ||
} | ||
const updatedState = new ValidationState(newPath, newSubgraphPaths, newOverrideConditions); | ||
const updatedState = new ValidationState(newPath, newSubgraphPathInfos, newOverrideConditions); | ||
let hint = undefined; | ||
if (newSubgraphPaths.length > 1 | ||
if (newSubgraphPathInfos.length > 1 | ||
&& transition.kind === 'FieldCollection' | ||
&& (0, federation_internals_1.isAbstractType)(newPath.tail.type) | ||
&& context.isShareable(transition.definition)) { | ||
const filteredPaths = newSubgraphPaths.map((p) => p.path).filter((p) => (0, federation_internals_1.isAbstractType)(p.tail.type)); | ||
const filteredPaths = newSubgraphPathInfos.map((p) => p.path.path).filter((p) => (0, federation_internals_1.isAbstractType)(p.tail.type)); | ||
if (filteredPaths.length > 1) { | ||
@@ -301,3 +322,3 @@ const allRuntimeTypes = possibleRuntimeTypeNamesSorted(newPath); | ||
let hasAllEmpty = true; | ||
for (const path of newSubgraphPaths) { | ||
for (const { path } of newSubgraphPathInfos) { | ||
const subgraph = path.path.tail.source; | ||
@@ -328,4 +349,4 @@ const typeNames = possibleRuntimeTypeNamesSorted(path.path); | ||
const subgraphs = []; | ||
for (const path of this.subgraphPaths) { | ||
const source = path.path.tail.source; | ||
for (const pathInfo of this.subgraphPathInfos) { | ||
const source = pathInfo.path.path.tail.source; | ||
if (!subgraphs.includes(source)) { | ||
@@ -337,11 +358,27 @@ subgraphs.push(source); | ||
} | ||
currentSubgraphContextKeys(subgraphNameToGraphEnumValue) { | ||
const subgraphContextKeys = new Set(); | ||
for (const pathInfo of this.subgraphPathInfos) { | ||
const tailSubgraphName = pathInfo.path.path.tail.source; | ||
const tailSubgraphEnumValue = subgraphNameToGraphEnumValue.get(tailSubgraphName); | ||
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}[${entryKeys.join(',')}]`); | ||
} | ||
return subgraphContextKeys; | ||
} | ||
currentSubgraphs() { | ||
if (this.subgraphPaths.length === 0) { | ||
if (this.subgraphPathInfos.length === 0) { | ||
return []; | ||
} | ||
const sources = this.subgraphPaths[0].path.graph.sources; | ||
const sources = this.subgraphPathInfos[0].path.path.graph.sources; | ||
return this.currentSubgraphNames().map((name) => ({ name, schema: sources.get(name) })); | ||
} | ||
toString() { | ||
return `${this.supergraphPath} <=> [${this.subgraphPaths.map(s => s.toString()).join(', ')}]`; | ||
return `${this.supergraphPath} <=> [${this.subgraphPathInfos.map(s => s.path.toString()).join(', ')}]`; | ||
} | ||
@@ -351,3 +388,4 @@ } | ||
function isSupersetOrEqual(maybeSuperset, other) { | ||
const includesAllSubgraphs = other.subgraphs.every((s) => maybeSuperset.subgraphs.includes(s)); | ||
const includesAllSubgraphs = [...other.subgraphContextKeys] | ||
.every((s) => maybeSuperset.subgraphContextKeys.has(s)); | ||
const includesAllOverrideConditions = [...other.overrideConditions.entries()].every(([label, value]) => maybeSuperset.overrideConditions.get(label) === value); | ||
@@ -357,3 +395,3 @@ return includesAllSubgraphs && includesAllOverrideConditions; | ||
class ValidationTraversal { | ||
constructor(supergraphSchema, supergraphAPI, federatedQueryGraph) { | ||
constructor(supergraphSchema, subgraphNameToGraphEnumValue, supergraphAPI, federatedQueryGraph) { | ||
this.stack = []; | ||
@@ -375,3 +413,3 @@ this.validationErrors = []; | ||
this.previousVisits = new query_graphs_1.QueryGraphState(supergraphAPI); | ||
this.context = new ValidationContext(supergraphSchema); | ||
this.context = new ValidationContext(supergraphSchema, subgraphNameToGraphEnumValue); | ||
} | ||
@@ -389,3 +427,3 @@ validate() { | ||
const currentVertexVisit = { | ||
subgraphs: state.currentSubgraphNames(), | ||
subgraphContextKeys: state.currentSubgraphContextKeys(this.context.subgraphNameToGraphEnumValue), | ||
overrideConditions: state.selectedOverrideConditions | ||
@@ -416,4 +454,7 @@ }; | ||
} | ||
const matchingContexts = edge.transition.kind === 'FieldCollection' | ||
? this.context.matchingContexts(edge.head.type.name) | ||
: []; | ||
debug.group(() => `Validating supergraph edge ${edge}`); | ||
const { state: newState, error, hint } = state.validateTransition(this.context, edge); | ||
const { state: newState, error, hint } = state.validateTransition(this.context, edge, matchingContexts); | ||
if (error) { | ||
@@ -420,0 +461,0 @@ debug.groupEnd(`Validation error!`); |
{ | ||
"name": "@apollo/composition", | ||
"version": "2.8.0-connectors.5", | ||
"version": "2.8.0", | ||
"description": "Apollo Federation composition utilities", | ||
@@ -30,4 +30,4 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"@apollo/federation-internals": "2.8.0-connectors.5", | ||
"@apollo/query-graphs": "2.8.0-connectors.5" | ||
"@apollo/federation-internals": "2.8.0", | ||
"@apollo/query-graphs": "2.8.0" | ||
}, | ||
@@ -34,0 +34,0 @@ "peerDependencies": { |
@@ -73,3 +73,8 @@ import { | ||
const federatedQueryGraph = buildFederatedQueryGraph(supergraph, false); | ||
const { errors, hints } = validateGraphComposition(supergraph.schema, supergraphQueryGraph, federatedQueryGraph); | ||
const { errors, hints } = validateGraphComposition( | ||
supergraph.schema, | ||
supergraph.subgraphNameToGraphEnumValue(), | ||
supergraphQueryGraph, | ||
federatedQueryGraph | ||
); | ||
if (errors) { | ||
@@ -76,0 +81,0 @@ return { errors }; |
@@ -68,2 +68,3 @@ import { | ||
'https://specs.apollo.dev/source', | ||
'https://specs.apollo.dev/context', | ||
]; | ||
@@ -180,2 +181,3 @@ | ||
sg.metadata().policyDirective(), | ||
sg.metadata().contextDirective(), | ||
].map(d => d.name); | ||
@@ -182,0 +184,0 @@ if (directivesComposedByDefault.includes(directive.name)) { |
@@ -215,2 +215,8 @@ import { NamedSchemaElement, SubgraphASTNode } from "@apollo/federation-internals"; | ||
const CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS = makeCodeDefinition({ | ||
code: 'CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS', | ||
level: HintLevel.INFO, | ||
description: 'Indicates that the argument will not be present in the supergraph because it is contextual in at least one subgraph.' | ||
}); | ||
export const HINTS = { | ||
@@ -246,2 +252,3 @@ INCONSISTENT_BUT_COMPATIBLE_FIELD_TYPE, | ||
IMPLICITLY_UPGRADED_FEDERATION_VERSION, | ||
CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS, | ||
} | ||
@@ -248,0 +255,0 @@ |
@@ -15,5 +15,7 @@ import { | ||
isDefined, | ||
isInterfaceType, | ||
isLeafType, | ||
isNullableType, | ||
isObjectType, | ||
isUnionType, | ||
joinStrings, | ||
@@ -38,2 +40,5 @@ MultiMap, | ||
JoinFieldDirectiveArguments, | ||
ContextSpecDefinition, | ||
CONTEXT_VERSIONS, | ||
NamedSchemaElement, | ||
} from "@apollo/federation-internals"; | ||
@@ -111,3 +116,3 @@ import { | ||
+ `\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.subgraphPaths.map((p) => p.path), witness); | ||
const error = new ValidationError(message, invalidState.supergraphPath, invalidState.subgraphPathInfos.map((p) => p.path.path), witness); | ||
return ERRORS.SHAREABLE_HAS_MISMATCHED_RUNTIME_TYPES.err(error.message, { | ||
@@ -307,2 +312,3 @@ nodes: subgraphNodes(invalidState, (s) => (s.type(field.parent.name) as CompositeType | undefined)?.field(field.name)?.sourceAST), | ||
supergraphSchema: Schema, | ||
subgraphNameToGraphEnumValue: Map<string, string>, | ||
supergraphAPI: QueryGraph, | ||
@@ -314,44 +320,11 @@ federatedQueryGraph: QueryGraph, | ||
} { | ||
const { errors, hints } = new ValidationTraversal(supergraphSchema, supergraphAPI, federatedQueryGraph).validate(); | ||
const { errors, hints } = new ValidationTraversal( | ||
supergraphSchema, | ||
subgraphNameToGraphEnumValue, | ||
supergraphAPI, | ||
federatedQueryGraph, | ||
).validate(); | ||
return errors.length > 0 ? { errors, hints } : { hints }; | ||
} | ||
// TODO: we don't use this anywhere, can we just remove it? | ||
export function computeSubgraphPaths( | ||
supergraphSchema: Schema, | ||
supergraphPath: RootPath<Transition>, | ||
federatedQueryGraph: QueryGraph, | ||
overrideConditions: Map<string, boolean>, | ||
): { | ||
traversal?: ValidationState, | ||
isComplete?: boolean, | ||
error?: GraphQLError | ||
} { | ||
try { | ||
assert(!supergraphPath.hasAnyEdgeConditions(), () => `A supergraph path should not have edge condition paths (as supergraph edges should not have conditions): ${supergraphPath}`); | ||
const conditionResolver = simpleValidationConditionResolver({ supergraph: supergraphSchema, queryGraph: federatedQueryGraph, withCaching: true }); | ||
const initialState = ValidationState.initial({ supergraphAPI: supergraphPath.graph, kind: supergraphPath.root.rootKind, federatedQueryGraph, conditionResolver, overrideConditions }); | ||
const context = new ValidationContext(supergraphSchema); | ||
let state = initialState; | ||
let isIncomplete = false; | ||
for (const [edge] of supergraphPath) { | ||
const { state: updated, error } = state.validateTransition(context, edge); | ||
if (error) { | ||
throw error; | ||
} | ||
if (!updated) { | ||
isIncomplete = true; | ||
break; | ||
} | ||
state = updated; | ||
} | ||
return {traversal: state, isComplete: !isIncomplete}; | ||
} catch (error) { | ||
if (error instanceof GraphQLError) { | ||
return {error}; | ||
} | ||
throw error; | ||
} | ||
} | ||
function initialSubgraphPaths(kind: SchemaRootKind, subgraphs: QueryGraph): RootPath<Transition>[] { | ||
@@ -383,5 +356,7 @@ const root = subgraphs.root(kind); | ||
private readonly joinFieldDirective: DirectiveDefinition<JoinFieldDirectiveArguments>; | ||
private readonly typesToContexts: Map<string, Set<string>> | ||
constructor( | ||
readonly supergraphSchema: Schema, | ||
readonly subgraphNameToGraphEnumValue: Map<string, string>, | ||
) { | ||
@@ -391,2 +366,34 @@ const [_, joinSpec] = validateSupergraph(supergraphSchema); | ||
this.joinFieldDirective = joinSpec.fieldDirective(supergraphSchema); | ||
this.typesToContexts = new Map(); | ||
let contextDirective: DirectiveDefinition<{ name: string }> | undefined; | ||
const contextFeature = supergraphSchema.coreFeatures?.getByIdentity(ContextSpecDefinition.identity); | ||
if (contextFeature) { | ||
const contextSpec = CONTEXT_VERSIONS.find(contextFeature.url.version); | ||
assert(contextSpec, `Unexpected context spec version ${contextFeature.url.version}`); | ||
contextDirective = contextSpec.contextDirective(supergraphSchema); | ||
} | ||
for (const application of contextDirective?.applications() ?? []) { | ||
const { name: context } = application.arguments(); | ||
assert( | ||
application.parent instanceof NamedSchemaElement, | ||
"Unexpectedly found unnamed element with @context" | ||
); | ||
const type = supergraphSchema.type(application.parent.name); | ||
assert(type, `Type ${application.parent.name} unexpectedly doesn't exist`); | ||
const typeNames = [type.name]; | ||
if (isInterfaceType(type)) { | ||
typeNames.push(...type.allImplementations().map((t) => t.name)); | ||
} else if (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])); | ||
} | ||
} | ||
} | ||
} | ||
@@ -414,4 +421,14 @@ | ||
} | ||
matchingContexts(typeName: string): string[] { | ||
return [...(this.typesToContexts.get(typeName) ?? [])]; | ||
} | ||
} | ||
type SubgraphPathInfo = { | ||
path: TransitionPathWithLazyIndirectPaths<RootVertex>, | ||
// The key for this map is the context name in the supergraph schema. | ||
contexts: Map<string, { subgraphName: string, typeName: string }>, | ||
} | ||
export class ValidationState { | ||
@@ -422,3 +439,3 @@ constructor( | ||
// All the possible paths we could be in the subgraph. | ||
public readonly subgraphPaths: TransitionPathWithLazyIndirectPaths<RootVertex>[], | ||
public readonly subgraphPathInfos: SubgraphPathInfo[], | ||
// When we encounter an `@override`n field with a label condition, we record | ||
@@ -453,3 +470,6 @@ // its value (T/F) as we traverse the graph. This allows us to ignore paths | ||
), | ||
), | ||
).map((p) => ({ | ||
path: p, | ||
contexts: new Map(), | ||
})), | ||
); | ||
@@ -469,3 +489,3 @@ } | ||
*/ | ||
validateTransition(context: ValidationContext, supergraphEdge: Edge): { | ||
validateTransition(context: ValidationContext, supergraphEdge: Edge, matchingContexts: string[]): { | ||
state?: ValidationState, | ||
@@ -479,3 +499,3 @@ error?: GraphQLError, | ||
const targetType = supergraphEdge.tail.type; | ||
const newSubgraphPaths: TransitionPathWithLazyIndirectPaths<RootVertex>[] = []; | ||
const newSubgraphPathInfos: SubgraphPathInfo[] = []; | ||
const deadEnds: Unadvanceables[] = []; | ||
@@ -492,3 +512,3 @@ // If the edge has an override condition, we should capture it in the state so | ||
for (const path of this.subgraphPaths) { | ||
for (const { path, contexts } of this.subgraphPathInfos) { | ||
const options = advancePathWithTransition( | ||
@@ -509,7 +529,23 @@ path, | ||
} | ||
newSubgraphPaths.push(...options); | ||
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 }))); | ||
} | ||
const newPath = this.supergraphPath.add(transition, supergraphEdge, noConditionsResolution); | ||
if (newSubgraphPaths.length === 0) { | ||
return { error: satisfiabilityError(newPath, this.subgraphPaths.map((p) => p.path), deadEnds) }; | ||
if (newSubgraphPathInfos.length === 0) { | ||
return { error: satisfiabilityError(newPath, this.subgraphPathInfos.map((p) => p.path.path), deadEnds) }; | ||
} | ||
@@ -519,3 +555,3 @@ | ||
newPath, | ||
newSubgraphPaths, | ||
newSubgraphPathInfos, | ||
newOverrideConditions, | ||
@@ -542,3 +578,3 @@ ); | ||
if ( | ||
newSubgraphPaths.length > 1 | ||
newSubgraphPathInfos.length > 1 | ||
&& transition.kind === 'FieldCollection' | ||
@@ -548,3 +584,3 @@ && isAbstractType(newPath.tail.type) | ||
) { | ||
const filteredPaths = newSubgraphPaths.map((p) => p.path).filter((p) => isAbstractType(p.tail.type)); | ||
const filteredPaths = newSubgraphPathInfos.map((p) => p.path.path).filter((p) => isAbstractType(p.tail.type)); | ||
if (filteredPaths.length > 1) { | ||
@@ -560,3 +596,3 @@ // We start our intersection by using all the supergraph types, both because it's a convenient "max" set to start our intersection, | ||
let hasAllEmpty = true; | ||
for (const path of newSubgraphPaths) { | ||
for (const { path } of newSubgraphPathInfos) { | ||
const subgraph = path.path.tail.source; | ||
@@ -600,4 +636,4 @@ const typeNames = possibleRuntimeTypeNamesSorted(path.path); | ||
const subgraphs: string[] = []; | ||
for (const path of this.subgraphPaths) { | ||
const source = path.path.tail.source; | ||
for (const pathInfo of this.subgraphPathInfos) { | ||
const source = pathInfo.path.path.tail.source; | ||
if (!subgraphs.includes(source)) { | ||
@@ -610,7 +646,26 @@ subgraphs.push(source); | ||
currentSubgraphContextKeys(subgraphNameToGraphEnumValue: Map<string, string>): Set<string> { | ||
const subgraphContextKeys: Set<string> = new Set(); | ||
for (const pathInfo of this.subgraphPathInfos) { | ||
const tailSubgraphName = pathInfo.path.path.tail.source; | ||
const tailSubgraphEnumValue = subgraphNameToGraphEnumValue.get(tailSubgraphName); | ||
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}[${entryKeys.join(',')}]` | ||
); | ||
} | ||
return subgraphContextKeys; | ||
} | ||
currentSubgraphs(): { name: string, schema: Schema }[] { | ||
if (this.subgraphPaths.length === 0) { | ||
if (this.subgraphPathInfos.length === 0) { | ||
return []; | ||
} | ||
const sources = this.subgraphPaths[0].path.graph.sources; | ||
const sources = this.subgraphPathInfos[0].path.path.graph.sources; | ||
return this.currentSubgraphNames().map((name) => ({ name, schema: sources.get(name)!})); | ||
@@ -620,3 +675,3 @@ } | ||
toString(): string { | ||
return `${this.supergraphPath} <=> [${this.subgraphPaths.map(s => s.toString()).join(', ')}]`; | ||
return `${this.supergraphPath} <=> [${this.subgraphPathInfos.map(s => s.path.toString()).join(', ')}]`; | ||
} | ||
@@ -628,3 +683,4 @@ } | ||
function isSupersetOrEqual(maybeSuperset: VertexVisit, other: VertexVisit): boolean { | ||
const includesAllSubgraphs = other.subgraphs.every((s) => maybeSuperset.subgraphs.includes(s)); | ||
const includesAllSubgraphs = [...other.subgraphContextKeys] | ||
.every((s) => maybeSuperset.subgraphContextKeys.has(s)); | ||
const includesAllOverrideConditions = [...other.overrideConditions.entries()].every(([label, value]) => | ||
@@ -638,3 +694,3 @@ maybeSuperset.overrideConditions.get(label) === value | ||
interface VertexVisit { | ||
subgraphs: string[]; | ||
subgraphContextKeys: Set<string>; | ||
overrideConditions: Map<string, boolean>; | ||
@@ -659,2 +715,3 @@ } | ||
supergraphSchema: Schema, | ||
subgraphNameToGraphEnumValue: Map<string, string>, | ||
supergraphAPI: QueryGraph, | ||
@@ -676,3 +733,6 @@ federatedQueryGraph: QueryGraph, | ||
this.previousVisits = new QueryGraphState(supergraphAPI); | ||
this.context = new ValidationContext(supergraphSchema); | ||
this.context = new ValidationContext( | ||
supergraphSchema, | ||
subgraphNameToGraphEnumValue, | ||
); | ||
} | ||
@@ -695,3 +755,3 @@ | ||
const currentVertexVisit: VertexVisit = { | ||
subgraphs: state.currentSubgraphNames(), | ||
subgraphContextKeys: state.currentSubgraphContextKeys(this.context.subgraphNameToGraphEnumValue), | ||
overrideConditions: state.selectedOverrideConditions | ||
@@ -740,5 +800,8 @@ }; | ||
const matchingContexts = edge.transition.kind === 'FieldCollection' | ||
? this.context.matchingContexts(edge.head.type.name) | ||
: []; | ||
debug.group(() => `Validating supergraph edge ${edge}`); | ||
const { state: newState, error, hint } = state.validateTransition(this.context, edge); | ||
const { state: newState, error, hint } = state.validateTransition(this.context, edge, matchingContexts); | ||
if (error) { | ||
@@ -745,0 +808,0 @@ debug.groupEnd(`Validation error!`); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
595592
8454
1
+ Added@apollo/federation-internals@2.8.0(transitive)
+ Added@apollo/query-graphs@2.8.0(transitive)
- Removed@apollo/federation-internals@2.8.0-connectors.5(transitive)
- Removed@apollo/query-graphs@2.8.0-connectors.5(transitive)
Updated@apollo/query-graphs@2.8.0