From 6cf28f266e6ceb03f01e6b709e667e9416964bab Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Wed, 26 Jun 2024 10:28:13 -0600 Subject: [PATCH] Split out satisfiability from `composeServices` (#3049) Create a `validateSatisfiability` function separate from `composeServces`. Allow fow disabling satisfiability when running `composeServices`. This is part of #3046, split out for easier review. --------- Co-authored-by: Taylor Jones <45475656+tayrrible@users.noreply.github.com> Co-authored-by: Taylor Jones --- composition-js/src/compose.ts | 112 ++++++++++++++++++++++++---------- 1 file changed, 81 insertions(+), 31 deletions(-) diff --git a/composition-js/src/compose.ts b/composition-js/src/compose.ts index 4c75e701e..8b17c4aea 100644 --- a/composition-js/src/compose.ts +++ b/composition-js/src/compose.ts @@ -14,7 +14,7 @@ import { } from "@apollo/federation-internals"; import { GraphQLError } from "graphql"; import { buildFederatedQueryGraph, buildSupergraphAPIQueryGraph } from "@apollo/query-graphs"; -import { mergeSubgraphs } from "./merging"; +import { MergeResult, mergeSubgraphs } from "./merging"; import { validateGraphComposition } from "./validate"; import { CompositionHint } from "./hints"; @@ -37,6 +37,8 @@ export interface CompositionSuccess { export interface CompositionOptions { sdlPrintOptions?: PrintOptions; allowedFieldTypeMergingSubtypingRules?: SubtypingRule[]; + /// Flag to toggle if satisfiability should be performed during composition + runSatisfiability?: boolean; } function validateCompositionOptions(options: CompositionOptions) { @@ -45,59 +47,58 @@ function validateCompositionOptions(options: CompositionOptions) { assert(!options?.allowedFieldTypeMergingSubtypingRules?.includes("list_upgrade"), "The `list_upgrade` field subtyping rule is currently not supported"); } +/** + * Used to compose a supergraph from subgraphs + * `options.runSatisfiability` will default to `true` + * + * @param subgraphs Subgraphs + * @param options CompositionOptions + */ export function compose(subgraphs: Subgraphs, options: CompositionOptions = {}): CompositionResult { - validateCompositionOptions(options); - - const upgradeResult = upgradeSubgraphsIfNecessary(subgraphs); - if (upgradeResult.errors) { - return { errors: upgradeResult.errors }; - } + const { runSatisfiability = true, sdlPrintOptions } = options; - const toMerge = upgradeResult.subgraphs; - const validationErrors = toMerge.validate(); - if (validationErrors) { - return { errors: validationErrors }; - } + validateCompositionOptions(options); - const mergeResult = mergeSubgraphs(toMerge); + const mergeResult = validateSubgraphsAndMerge(subgraphs); if (mergeResult.errors) { return { errors: mergeResult.errors }; } - // We pass `null` for the `supportedFeatures` to disable the feature support validation. Validating feature support - // is useful when executing/handling a supergraph, but here we're just validating the supergraph we've just created, - // and there is no reason to error due to an unsupported feature. - const supergraph = new Supergraph(mergeResult.supergraph, null); - const supergraphQueryGraph = buildSupergraphAPIQueryGraph(supergraph); - const federatedQueryGraph = buildFederatedQueryGraph(supergraph, false); - const { errors, hints } = validateGraphComposition( - supergraph.schema, - supergraph.subgraphNameToGraphEnumValue(), - supergraphQueryGraph, - federatedQueryGraph - ); - if (errors) { - return { errors }; + let satisfiabilityResult; + if (runSatisfiability) { + satisfiabilityResult = validateSatisfiability({ + supergraphSchema: mergeResult.supergraph + }); + if (satisfiabilityResult.errors) { + return { errors: satisfiabilityResult.errors }; + } } // printSchema calls validateOptions, which can throw let supergraphSdl; try { supergraphSdl = printSchema( - supergraph.schema, - options.sdlPrintOptions ?? shallowOrderPrintedDefinitions(defaultPrintOptions), + mergeResult.supergraph, + sdlPrintOptions ?? shallowOrderPrintedDefinitions(defaultPrintOptions), ); } catch (err) { return { errors: [err] }; } return { - schema: supergraph.schema, + schema: mergeResult.supergraph, supergraphSdl, - hints: mergeResult.hints.concat(hints ?? []), + hints: [...mergeResult.hints, ...(satisfiabilityResult?.hints ?? [])], }; } +/** + * Method to validate and compose services + * + * @param services List of Service definitions + * @param options CompositionOptions + * @returns CompositionResult + */ export function composeServices(services: ServiceDefinition[], options: CompositionOptions = {}): CompositionResult { const subgraphs = subgraphsFromServiceList(services); if (Array.isArray(subgraphs)) { @@ -106,5 +107,54 @@ export function composeServices(services: ServiceDefinition[], options: Composit // include the subgraph name in their message. return { errors: subgraphs }; } + return compose(subgraphs, options); } + +type SatisfiabilityArgs = { + supergraphSchema: Schema + supergraphSdl?: never +} | { supergraphSdl: string, supergraphSchema?: never }; + +/** + * Run satisfiability check for a supergraph + * + * Can pass either the supergraph's Schema or SDL to validate + * @param args: SatisfiabilityArgs + * @returns { errors? : GraphQLError[], hints? : CompositionHint[] } + */ +export function validateSatisfiability({ supergraphSchema, supergraphSdl} : SatisfiabilityArgs) : { + errors? : GraphQLError[], + hints? : CompositionHint[], +} { + // We pass `null` for the `supportedFeatures` to disable the feature support validation. Validating feature support + // is useful when executing/handling a supergraph, but here we're just validating the supergraph we've just created, + // and there is no reason to error due to an unsupported feature. + const supergraph = supergraphSchema ? new Supergraph(supergraphSchema, null) : Supergraph.build(supergraphSdl); + const supergraphQueryGraph = buildSupergraphAPIQueryGraph(supergraph); + const federatedQueryGraph = buildFederatedQueryGraph(supergraph, false); + return validateGraphComposition(supergraph.schema, supergraph.subgraphNameToGraphEnumValue(), supergraphQueryGraph, federatedQueryGraph); +} + +type ValidateSubgraphsAndMergeResult = MergeResult | { errors: GraphQLError[] }; + +/** + * Upgrade subgraphs if necessary, then validates subgraphs before attempting to merge + * + * @param subgraphs + * @returns ValidateSubgraphsAndMergeResult + */ +function validateSubgraphsAndMerge(subgraphs: Subgraphs) : ValidateSubgraphsAndMergeResult { + const upgradeResult = upgradeSubgraphsIfNecessary(subgraphs); + if (upgradeResult.errors) { + return { errors: upgradeResult.errors }; + } + + const toMerge = upgradeResult.subgraphs; + const validationErrors = toMerge.validate(); + if (validationErrors) { + return { errors: validationErrors }; + } + + return mergeSubgraphs(toMerge); +}