Skip to content

Commit b734ea0

Browse files
lennyburdetteMeschreiberDaleSeosachindshindetninesling
authored
merge main (#3275)
<!-- First, 🌠 thank you 🌠 for taking the time to consider a contribution to Apollo! Here are some important details to follow: * ⏰ Your time is important To save your precious time, if the contribution you are making will take more than an hour, please make sure it has been discussed in an issue first. This is especially true for feature requests! * 💡 Features Feature requests can be created and discussed within a GitHub Issue. Be sure to search for existing feature requests (and related issues!) prior to opening a new request. If an existing issue covers the need, please upvote that issue by using the 👍 emote, rather than opening a new issue. * 🕷 Bug fixes These can be created and discussed in this repository. When fixing a bug, please _try_ to add a test which verifies the fix. If you cannot, you should still submit the PR but we may still ask you (and help you!) to create a test. * Federation versions Please make sure you're targeting the federation version you're opening the PR for. Federation 2 (alpha) is currently located on the `main` branch and prior versions of Federation live on the `version-0.x` branch. * 📖 Contribution guidelines Follow https://github.com/apollographql/federation/blob/HEAD/CONTRIBUTING.md when submitting a pull request. Make sure existing tests still pass, and add tests for all new behavior. * ✏️ Explain your pull request Describe the big picture of your changes here to communicate to what your pull request is meant to accomplish. Provide 🔗 links 🔗 to associated issues! We hope you will find this to be a positive experience! Open source contribution can be intimidating and we hope to alleviate that pain as much as possible. Without following these guidelines, you may be missing context that can help you succeed with your contribution, which is why we encourage discussion first. Ultimately, there is no guarantee that we will be able to merge your pull-request, but by following these guidelines we can try to avoid disappointment. --> --------- Co-authored-by: Maria Elisabeth Schreiber <maria.schreiber@apollographql.com> Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com> Co-authored-by: Sachin D. Shinde <sachin@apollographql.com> Co-authored-by: Taylor Ninesling <taylor.ninesling@apollographql.com> Co-authored-by: Dylan Anthony <dylan@apollographql.com> Co-authored-by: Ben Newman <ben@apollographql.com> Co-authored-by: Taylor Jones <45475656+tayrrible@users.noreply.github.com> Co-authored-by: Taylor Jones <taylor@apollographql.com> Co-authored-by: Nicholas Cioli <nicholas.cioli@apollographql.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Chris Lenfest <clenfest@apollographql.com> Co-authored-by: Edward Huang <edward.huang@apollographql.com> Co-authored-by: Matt Peake <7741049+peakematt@users.noreply.github.com> Co-authored-by: kamila-brylewska-zendesk <kamila.brylewska@zendesk.com> Co-authored-by: Andrew McGivery <andrew.mcgivery@apollographql.com> Co-authored-by: Duckki Oe <duckki.oe@apollographql.com> Co-authored-by: Liz Hennessy <95302380+lizhennessy@users.noreply.github.com> Co-authored-by: Shane Myrick <shane@apollographql.com>
1 parent 3739dfa commit b734ea0

File tree

27 files changed

+294
-133
lines changed

27 files changed

+294
-133
lines changed

.changeset/five-suits-drum.md

Lines changed: 0 additions & 10 deletions
This file was deleted.

.changeset/four-panthers-itch.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@apollo/composition": patch
3+
"@apollo/federation-internals": patch
4+
---
5+
6+
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.

composition-js/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# CHANGELOG for `@apollo/composition`
22

3+
## 2.11.0
4+
5+
### Minor Changes
6+
7+
- Adds connect spec v0.2, available for use with Apollo Router 2.3.0 or greater. ([#3262](https://github.com/apollographql/federation/pull/3262))
8+
9+
### Patch Changes
10+
11+
- Allow merging external types when using arrays as default arguments. ([#3262](https://github.com/apollographql/federation/pull/3262))
12+
13+
- Updated dependencies [[`1462c91879d41884c0a7e60551d8dd0d67c832d3`](https://github.com/apollographql/federation/commit/1462c91879d41884c0a7e60551d8dd0d67c832d3), [`9614b26e5a17cbf1f6aaf08f6fcb1c95eb12592d`](https://github.com/apollographql/federation/commit/9614b26e5a17cbf1f6aaf08f6fcb1c95eb12592d)]:
14+
- @apollo/query-graphs@2.11.0
15+
- @apollo/federation-internals@2.11.0
16+
317
## 2.11.0-preview.2
418

519
### Patch Changes

composition-js/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@apollo/composition",
3-
"version": "2.11.0-preview.2",
3+
"version": "2.11.0",
44
"description": "Apollo Federation composition utilities",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
@@ -27,10 +27,10 @@
2727
"access": "public"
2828
},
2929
"dependencies": {
30-
"@apollo/federation-internals": "2.11.0-preview.2",
31-
"@apollo/query-graphs": "2.11.0-preview.2"
30+
"@apollo/federation-internals": "2.11.0",
31+
"@apollo/query-graphs": "2.11.0"
3232
},
3333
"peerDependencies": {
3434
"graphql": "^16.5.0"
3535
}
36-
}
36+
}

composition-js/src/__tests__/validation_errors.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,3 +412,80 @@ describe('when shared field has non-intersecting runtime types in different subg
412412
]);
413413
});
414414
});
415+
416+
describe('other validation errors', () => {
417+
418+
it('errors when maxValidationSubgraphPaths is exceeded', () => {
419+
const subgraphA = {
420+
name: 'A',
421+
typeDefs: gql`
422+
type Query {
423+
a: A
424+
}
425+
426+
type A @key(fields: "id") {
427+
id: ID!
428+
b: B
429+
c: C
430+
d: D
431+
}
432+
433+
type B @key(fields: "id") {
434+
id: ID!
435+
a: A @shareable
436+
b: Int @shareable
437+
c: C @shareable
438+
d: D @shareable
439+
}
440+
441+
type C @key(fields: "id") {
442+
id: ID!
443+
a: A @shareable
444+
b: B @shareable
445+
c: Int @shareable
446+
d: D @shareable
447+
}
448+
449+
type D @key(fields: "id") {
450+
id: ID!
451+
a: A @shareable
452+
b: B @shareable
453+
c: C @shareable
454+
d: Int @shareable
455+
}
456+
`
457+
};
458+
const subgraphB = {
459+
name: 'B',
460+
typeDefs: gql`
461+
type B @key(fields: "id") {
462+
id: ID!
463+
b: Int @shareable
464+
c: C @shareable
465+
d: D @shareable
466+
}
467+
468+
type C @key(fields: "id") {
469+
id: ID!
470+
b: B @shareable
471+
c: Int @shareable
472+
d: D @shareable
473+
}
474+
475+
type D @key(fields: "id") {
476+
id: ID!
477+
b: B @shareable
478+
c: C @shareable
479+
d: Int @shareable
480+
}
481+
`
482+
};
483+
const result = composeAsFed2Subgraphs([subgraphA, subgraphB], { maxValidationSubgraphPaths: 10 });
484+
expect(result.errors).toBeDefined();
485+
expect(errorMessages(result)).toMatchStringArray([
486+
`
487+
Maximum number of validation subgraph paths exceeded: 12
488+
`
489+
]);
490+
});
491+
});

composition-js/src/compose.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export interface CompositionOptions {
3939
allowedFieldTypeMergingSubtypingRules?: SubtypingRule[];
4040
/// Flag to toggle if satisfiability should be performed during composition
4141
runSatisfiability?: boolean;
42+
/// Maximum allowable number of outstanding subgraph paths to validate
43+
maxValidationSubgraphPaths?: number;
4244
}
4345

4446
function validateCompositionOptions(options: CompositionOptions) {
@@ -55,7 +57,7 @@ function validateCompositionOptions(options: CompositionOptions) {
5557
* @param options CompositionOptions
5658
*/
5759
export function compose(subgraphs: Subgraphs, options: CompositionOptions = {}): CompositionResult {
58-
const { runSatisfiability = true, sdlPrintOptions } = options;
60+
const { runSatisfiability = true, sdlPrintOptions, maxValidationSubgraphPaths } = options;
5961

6062
validateCompositionOptions(options);
6163

@@ -67,8 +69,8 @@ export function compose(subgraphs: Subgraphs, options: CompositionOptions = {}):
6769
let satisfiabilityResult;
6870
if (runSatisfiability) {
6971
satisfiabilityResult = validateSatisfiability({
70-
supergraphSchema: mergeResult.supergraph
71-
});
72+
supergraphSchema: mergeResult.supergraph,
73+
}, { maxValidationSubgraphPaths });
7274
if (satisfiabilityResult.errors) {
7375
return { errors: satisfiabilityResult.errors };
7476
}
@@ -123,7 +125,7 @@ type SatisfiabilityArgs = {
123125
* @param args: SatisfiabilityArgs
124126
* @returns { errors? : GraphQLError[], hints? : CompositionHint[] }
125127
*/
126-
export function validateSatisfiability({ supergraphSchema, supergraphSdl} : SatisfiabilityArgs) : {
128+
export function validateSatisfiability({ supergraphSchema, supergraphSdl} : SatisfiabilityArgs, options: CompositionOptions = {}) : {
127129
errors? : GraphQLError[],
128130
hints? : CompositionHint[],
129131
} {
@@ -133,7 +135,7 @@ export function validateSatisfiability({ supergraphSchema, supergraphSdl} : Sati
133135
const supergraph = supergraphSchema ? new Supergraph(supergraphSchema, null) : Supergraph.build(supergraphSdl, { supportedFeatures: null });
134136
const supergraphQueryGraph = buildSupergraphAPIQueryGraph(supergraph);
135137
const federatedQueryGraph = buildFederatedQueryGraph(supergraph, false);
136-
return validateGraphComposition(supergraph.schema, supergraph.subgraphNameToGraphEnumValue(), supergraphQueryGraph, federatedQueryGraph);
138+
return validateGraphComposition(supergraph.schema, supergraph.subgraphNameToGraphEnumValue(), supergraphQueryGraph, federatedQueryGraph, options);
137139
}
138140

139141
type ValidateSubgraphsAndMergeResult = MergeResult | { errors: GraphQLError[] };

composition-js/src/validate.ts

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import {
6262
} from "@apollo/query-graphs";
6363
import { CompositionHint, HINTS } from "./hints";
6464
import { ASTNode, GraphQLError, print } from "graphql";
65+
import { CompositionOptions } from './compose';
6566

6667
const debug = newDebugLogger('validation');
6768

@@ -310,6 +311,7 @@ export function validateGraphComposition(
310311
subgraphNameToGraphEnumValue: Map<string, string>,
311312
supergraphAPI: QueryGraph,
312313
federatedQueryGraph: QueryGraph,
314+
compositionOptions: CompositionOptions = {},
313315
): {
314316
errors? : GraphQLError[],
315317
hints? : CompositionHint[],
@@ -319,6 +321,7 @@ export function validateGraphComposition(
319321
subgraphNameToGraphEnumValue,
320322
supergraphAPI,
321323
federatedQueryGraph,
324+
compositionOptions,
322325
).validate();
323326
return errors.length > 0 ? { errors, hints } : { hints };
324327
}
@@ -695,19 +698,26 @@ class ValidationTraversal {
695698
private readonly validationHints: CompositionHint[] = [];
696699

697700
private readonly context: ValidationContext;
698-
701+
private totalValidationSubgraphPaths = 0;
702+
private maxValidationSubgraphPaths: number;
703+
704+
private static DEFAULT_MAX_VALIDATION_SUBGRAPH_PATHS = 1000000;
705+
699706
constructor(
700707
supergraphSchema: Schema,
701708
subgraphNameToGraphEnumValue: Map<string, string>,
702709
supergraphAPI: QueryGraph,
703710
federatedQueryGraph: QueryGraph,
711+
compositionOptions: CompositionOptions,
704712
) {
713+
this.maxValidationSubgraphPaths = compositionOptions.maxValidationSubgraphPaths ?? ValidationTraversal.DEFAULT_MAX_VALIDATION_SUBGRAPH_PATHS;
714+
705715
this.conditionResolver = simpleValidationConditionResolver({
706716
supergraph: supergraphSchema,
707717
queryGraph: federatedQueryGraph,
708718
withCaching: true,
709719
});
710-
supergraphAPI.rootKinds().forEach((kind) => this.stack.push(ValidationState.initial({
720+
supergraphAPI.rootKinds().forEach((kind) => this.pushStack(ValidationState.initial({
711721
supergraphAPI,
712722
kind,
713723
federatedQueryGraph,
@@ -720,18 +730,38 @@ class ValidationTraversal {
720730
subgraphNameToGraphEnumValue,
721731
);
722732
}
733+
734+
pushStack(state: ValidationState): { error?: GraphQLError } {
735+
this.totalValidationSubgraphPaths += state.subgraphPathInfos.length;
736+
this.stack.push(state);
737+
if (this.totalValidationSubgraphPaths > this.maxValidationSubgraphPaths) {
738+
return { error: ERRORS.MAX_VALIDATION_SUBGRAPH_PATHS_EXCEEDED.err(`Maximum number of validation subgraph paths exceeded: ${this.totalValidationSubgraphPaths}`) };
739+
}
740+
return {};
741+
}
742+
743+
popStack() {
744+
const state = this.stack.pop();
745+
if (state) {
746+
this.totalValidationSubgraphPaths -= state.subgraphPathInfos.length;
747+
}
748+
return state;
749+
}
723750

724751
validate(): {
725752
errors: GraphQLError[],
726753
hints: CompositionHint[],
727754
} {
728755
while (this.stack.length > 0) {
729-
this.handleState(this.stack.pop()!);
756+
const { error } = this.handleState(this.popStack()!);
757+
if (error) {
758+
return { errors: [error], hints: this.validationHints };
759+
}
730760
}
731761
return { errors: this.validationErrors, hints: this.validationHints };
732762
}
733763

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

@@ -748,7 +778,7 @@ class ValidationTraversal {
748778
// type, and have strictly more options regarding subgraphs. So whatever comes next, we can handle in the exact
749779
// same way we did previously, and there is thus no way to bother.
750780
debug.groupEnd(`Has already validated this vertex.`);
751-
return;
781+
return {};
752782
}
753783
}
754784
// We're gonna have to validate, but we can save the new set of sources here to hopefully save work later.
@@ -799,12 +829,16 @@ class ValidationTraversal {
799829
// state to the stack this method, `handleState`, will do nothing later. But it's
800830
// worth checking it now and save some memory/cycles.
801831
if (newState && !newState.supergraphPath.isTerminal()) {
802-
this.stack.push(newState);
832+
const { error } = this.pushStack(newState);
833+
if (error) {
834+
return { error };
835+
}
803836
debug.groupEnd(() => `Reached new state ${newState}`);
804837
} else {
805838
debug.groupEnd(`Reached terminal vertex/cycle`);
806839
}
807840
}
808841
debug.groupEnd();
842+
return {};
809843
}
810844
}

docs/source/schema-design/federated-schemas/entities/contribute-fields.mdx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,9 @@ type Product @key(fields: "id") {
104104
In this modification of the previous example, `size` and `weight` are now subfields of a `ProductDimensions` object.
105105
The Products and Shipping subgraphs must both define the `ProductDimensions` type for this to be valid.
106106

107-
<MinVersion version="2.1.2">
108-
109107
### Using `@requires` with fields that take arguments
110108

111-
</MinVersion>
109+
<MinVersionBadge version="Federation v2.1.2" />
112110

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

docs/source/schema-design/federated-schemas/entities/interfaces.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: Entity Interfaces
33
subtitle: Add entity fields polymorphically
44
description: Discover how to efficiently add polymorphic fields to GraphQL interfaces using Apollo Federation's Entity Interfaces and the @interfaceObject directive.
5-
minVersion: 2.3
5+
minVersion: Federation v2.3
66
---
77

88
Apollo Federation provides powerful extensions to GraphQL interfaces, specifically for use with your supergraph's [entities](/graphos/get-started/guides/federate-schemas#entity-overview):

docs/source/schema-design/federated-schemas/entities/migrate-fields.mdx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,9 @@ type Bill @key(fields: "id") {
156156

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

159-
<MinVersion version="2.7">
160-
161159
## Incremental migration with progressive `@override`
162160

163-
</MinVersion>
161+
<MinVersionBadge version="Federation v2.7" />
164162

165163
<ProgressiveOverrideEnterprise/>
166164

0 commit comments

Comments
 (0)