Skip to content
Merged
Show file tree
Hide file tree
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
10 changes: 0 additions & 10 deletions .changeset/five-suits-drum.md

This file was deleted.

6 changes: 6 additions & 0 deletions .changeset/four-panthers-itch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@apollo/composition": patch
"@apollo/federation-internals": patch
---

Adding new CompositionOption `maxValidationSubgraphPaths`. This value represents the maximum number of SubgraphPathInfo objects that may exist in a ValidationTraversal when checking for satisfiability. Setting this value can help composition error before running out of memory. Default is 1,000,000.
14 changes: 14 additions & 0 deletions composition-js/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# CHANGELOG for `@apollo/composition`

## 2.11.0

### Minor Changes

- Adds connect spec v0.2, available for use with Apollo Router 2.3.0 or greater. ([#3262](https://github.com/apollographql/federation/pull/3262))

### Patch Changes

- Allow merging external types when using arrays as default arguments. ([#3262](https://github.com/apollographql/federation/pull/3262))

- Updated dependencies [[`1462c91879d41884c0a7e60551d8dd0d67c832d3`](https://github.com/apollographql/federation/commit/1462c91879d41884c0a7e60551d8dd0d67c832d3), [`9614b26e5a17cbf1f6aaf08f6fcb1c95eb12592d`](https://github.com/apollographql/federation/commit/9614b26e5a17cbf1f6aaf08f6fcb1c95eb12592d)]:
- @apollo/query-graphs@2.11.0
- @apollo/federation-internals@2.11.0

## 2.11.0-preview.2

### Patch Changes
Expand Down
8 changes: 4 additions & 4 deletions composition-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@apollo/composition",
"version": "2.11.0-preview.2",
"version": "2.11.0",
"description": "Apollo Federation composition utilities",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand All @@ -27,10 +27,10 @@
"access": "public"
},
"dependencies": {
"@apollo/federation-internals": "2.11.0-preview.2",
"@apollo/query-graphs": "2.11.0-preview.2"
"@apollo/federation-internals": "2.11.0",
"@apollo/query-graphs": "2.11.0"
},
"peerDependencies": {
"graphql": "^16.5.0"
}
}
}
77 changes: 77 additions & 0 deletions composition-js/src/__tests__/validation_errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,3 +412,80 @@ describe('when shared field has non-intersecting runtime types in different subg
]);
});
});

describe('other validation errors', () => {

it('errors when maxValidationSubgraphPaths is exceeded', () => {
const subgraphA = {
name: 'A',
typeDefs: gql`
type Query {
a: A
}

type A @key(fields: "id") {
id: ID!
b: B
c: C
d: D
}

type B @key(fields: "id") {
id: ID!
a: A @shareable
b: Int @shareable
c: C @shareable
d: D @shareable
}

type C @key(fields: "id") {
id: ID!
a: A @shareable
b: B @shareable
c: Int @shareable
d: D @shareable
}

type D @key(fields: "id") {
id: ID!
a: A @shareable
b: B @shareable
c: C @shareable
d: Int @shareable
}
`
};
const subgraphB = {
name: 'B',
typeDefs: gql`
type B @key(fields: "id") {
id: ID!
b: Int @shareable
c: C @shareable
d: D @shareable
}

type C @key(fields: "id") {
id: ID!
b: B @shareable
c: Int @shareable
d: D @shareable
}

type D @key(fields: "id") {
id: ID!
b: B @shareable
c: C @shareable
d: Int @shareable
}
`
};
const result = composeAsFed2Subgraphs([subgraphA, subgraphB], { maxValidationSubgraphPaths: 10 });
expect(result.errors).toBeDefined();
expect(errorMessages(result)).toMatchStringArray([
`
Maximum number of validation subgraph paths exceeded: 12
`
]);
});
});
12 changes: 7 additions & 5 deletions composition-js/src/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export interface CompositionOptions {
allowedFieldTypeMergingSubtypingRules?: SubtypingRule[];
/// Flag to toggle if satisfiability should be performed during composition
runSatisfiability?: boolean;
/// Maximum allowable number of outstanding subgraph paths to validate
maxValidationSubgraphPaths?: number;
}

function validateCompositionOptions(options: CompositionOptions) {
Expand All @@ -55,7 +57,7 @@ function validateCompositionOptions(options: CompositionOptions) {
* @param options CompositionOptions
*/
export function compose(subgraphs: Subgraphs, options: CompositionOptions = {}): CompositionResult {
const { runSatisfiability = true, sdlPrintOptions } = options;
const { runSatisfiability = true, sdlPrintOptions, maxValidationSubgraphPaths } = options;

validateCompositionOptions(options);

Expand All @@ -67,8 +69,8 @@ export function compose(subgraphs: Subgraphs, options: CompositionOptions = {}):
let satisfiabilityResult;
if (runSatisfiability) {
satisfiabilityResult = validateSatisfiability({
supergraphSchema: mergeResult.supergraph
});
supergraphSchema: mergeResult.supergraph,
}, { maxValidationSubgraphPaths });
if (satisfiabilityResult.errors) {
return { errors: satisfiabilityResult.errors };
}
Expand Down Expand Up @@ -123,7 +125,7 @@ type SatisfiabilityArgs = {
* @param args: SatisfiabilityArgs
* @returns { errors? : GraphQLError[], hints? : CompositionHint[] }
*/
export function validateSatisfiability({ supergraphSchema, supergraphSdl} : SatisfiabilityArgs) : {
export function validateSatisfiability({ supergraphSchema, supergraphSdl} : SatisfiabilityArgs, options: CompositionOptions = {}) : {
errors? : GraphQLError[],
hints? : CompositionHint[],
} {
Expand All @@ -133,7 +135,7 @@ export function validateSatisfiability({ supergraphSchema, supergraphSdl} : Sati
const supergraph = supergraphSchema ? new Supergraph(supergraphSchema, null) : Supergraph.build(supergraphSdl, { supportedFeatures: null });
const supergraphQueryGraph = buildSupergraphAPIQueryGraph(supergraph);
const federatedQueryGraph = buildFederatedQueryGraph(supergraph, false);
return validateGraphComposition(supergraph.schema, supergraph.subgraphNameToGraphEnumValue(), supergraphQueryGraph, federatedQueryGraph);
return validateGraphComposition(supergraph.schema, supergraph.subgraphNameToGraphEnumValue(), supergraphQueryGraph, federatedQueryGraph, options);
}

type ValidateSubgraphsAndMergeResult = MergeResult | { errors: GraphQLError[] };
Expand Down
46 changes: 40 additions & 6 deletions composition-js/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import {
} from "@apollo/query-graphs";
import { CompositionHint, HINTS } from "./hints";
import { ASTNode, GraphQLError, print } from "graphql";
import { CompositionOptions } from './compose';

const debug = newDebugLogger('validation');

Expand Down Expand Up @@ -310,6 +311,7 @@ export function validateGraphComposition(
subgraphNameToGraphEnumValue: Map<string, string>,
supergraphAPI: QueryGraph,
federatedQueryGraph: QueryGraph,
compositionOptions: CompositionOptions = {},
): {
errors? : GraphQLError[],
hints? : CompositionHint[],
Expand All @@ -319,6 +321,7 @@ export function validateGraphComposition(
subgraphNameToGraphEnumValue,
supergraphAPI,
federatedQueryGraph,
compositionOptions,
).validate();
return errors.length > 0 ? { errors, hints } : { hints };
}
Expand Down Expand Up @@ -695,19 +698,26 @@ class ValidationTraversal {
private readonly validationHints: CompositionHint[] = [];

private readonly context: ValidationContext;

private totalValidationSubgraphPaths = 0;
private maxValidationSubgraphPaths: number;

private static DEFAULT_MAX_VALIDATION_SUBGRAPH_PATHS = 1000000;

constructor(
supergraphSchema: Schema,
subgraphNameToGraphEnumValue: Map<string, string>,
supergraphAPI: QueryGraph,
federatedQueryGraph: QueryGraph,
compositionOptions: CompositionOptions,
) {
this.maxValidationSubgraphPaths = compositionOptions.maxValidationSubgraphPaths ?? ValidationTraversal.DEFAULT_MAX_VALIDATION_SUBGRAPH_PATHS;

this.conditionResolver = simpleValidationConditionResolver({
supergraph: supergraphSchema,
queryGraph: federatedQueryGraph,
withCaching: true,
});
supergraphAPI.rootKinds().forEach((kind) => this.stack.push(ValidationState.initial({
supergraphAPI.rootKinds().forEach((kind) => this.pushStack(ValidationState.initial({
supergraphAPI,
kind,
federatedQueryGraph,
Expand All @@ -720,18 +730,38 @@ class ValidationTraversal {
subgraphNameToGraphEnumValue,
);
}

pushStack(state: ValidationState): { error?: GraphQLError } {
this.totalValidationSubgraphPaths += state.subgraphPathInfos.length;
this.stack.push(state);
if (this.totalValidationSubgraphPaths > this.maxValidationSubgraphPaths) {
return { error: 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.subgraphPathInfos.length;
}
return state;
}

validate(): {
errors: GraphQLError[],
hints: CompositionHint[],
} {
while (this.stack.length > 0) {
this.handleState(this.stack.pop()!);
const { error } = this.handleState(this.popStack()!);
if (error) {
return { errors: [error], hints: this.validationHints };
}
}
return { errors: this.validationErrors, hints: this.validationHints };
}

private handleState(state: ValidationState) {
private handleState(state: ValidationState): { error?: GraphQLError } {
debug.group(() => `Validation: ${this.stack.length + 1} open states. Validating ${state}`);
const vertex = state.supergraphPath.tail;

Expand All @@ -748,7 +778,7 @@ class ValidationTraversal {
// type, and have strictly more options regarding subgraphs. So whatever comes next, we can handle in the exact
// same way we did previously, and there is thus no way to bother.
debug.groupEnd(`Has already validated this vertex.`);
return;
return {};
}
}
// We're gonna have to validate, but we can save the new set of sources here to hopefully save work later.
Expand Down Expand Up @@ -799,12 +829,16 @@ class ValidationTraversal {
// state to the stack this method, `handleState`, will do nothing later. But it's
// worth checking it now and save some memory/cycles.
if (newState && !newState.supergraphPath.isTerminal()) {
this.stack.push(newState);
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 {};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,9 @@ type Product @key(fields: "id") {
In this modification of the previous example, `size` and `weight` are now subfields of a `ProductDimensions` object.
The Products and Shipping subgraphs must both define the `ProductDimensions` type for this to be valid.

<MinVersion version="2.1.2">

### Using `@requires` with fields that take arguments

</MinVersion>
<MinVersionBadge version="Federation v2.1.2" />

Starting in Federation v2.1.2, the `@requires` directive can include fields that take arguments, like so:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Entity Interfaces
subtitle: Add entity fields polymorphically
description: Discover how to efficiently add polymorphic fields to GraphQL interfaces using Apollo Federation's Entity Interfaces and the @interfaceObject directive.
minVersion: 2.3
minVersion: Federation v2.3
---

Apollo Federation provides powerful extensions to GraphQL interfaces, specifically for use with your supergraph's [entities](/graphos/get-started/guides/federate-schemas#entity-overview):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,9 @@ type Bill @key(fields: "id") {

After you deploy the Billing subgraph and publish this final schema change, you've migrated `Bill.amount` to the Billing subgraph with zero downtime.

<MinVersion version="2.7">

## Incremental migration with progressive `@override`

</MinVersion>
<MinVersionBadge version="Federation v2.7" />

<ProgressiveOverrideEnterprise/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Use contexts to share data
subtitle: Share data along type hierarchies without overloading @keys
description: Use the @context and @fromContext directives to enable a subgraph to share fields. These directives define contexts that enable data sharing along type hierarchies.
minVersion: 2.8
minVersion: Federation v2.8
---

<EnterpriseFeature>
Expand Down
Loading