Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}