Skip to content

Commit

Permalink
Split out satisfiability from composeServices (#3049)
Browse files Browse the repository at this point in the history
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 <[email protected]>
Co-authored-by: Taylor Jones <[email protected]>
  • Loading branch information
3 people authored Jun 26, 2024
1 parent 5a72dd8 commit 6cf28f2
Showing 1 changed file with 81 additions and 31 deletions.
112 changes: 81 additions & 31 deletions composition-js/src/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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) {
Expand All @@ -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)) {
Expand All @@ -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);
}

0 comments on commit 6cf28f2

Please sign in to comment.