Skip to content

Commit 711baef

Browse files
committed
Merge branch 'main' into weatherman/aggregate-invalid-inaccessible
2 parents 5cb1d6c + 41ccc43 commit 711baef

31 files changed

+552
-64
lines changed

.circleci/config.yml

+11
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ jobs:
6464
steps:
6565
- common_test_steps
6666

67+
NodeJS 17:
68+
docker:
69+
- image: cimg/node:17.0.0
70+
steps:
71+
- common_test_steps
72+
6773
GraphQL Types:
6874
description: "Assert generated GraphQL types are up to date"
6975
docker:
@@ -120,6 +126,9 @@ workflows:
120126
- NodeJS 16:
121127
name: "JS: Node 16"
122128
<<: *common_non_publish_filters
129+
- NodeJS 17:
130+
name: "JS: Node 17"
131+
<<: *common_non_publish_filters
123132
- GraphQL Types:
124133
name: "GraphQL Types (up to date)"
125134
<<: *common_non_publish_filters
@@ -133,6 +142,7 @@ workflows:
133142
- "JS: Node 12"
134143
- "JS: Node 14"
135144
- "JS: Node 16"
145+
- "JS: Node 17"
136146
- "GraphQL Types (up to date)"
137147
- "Error code Doc (up to date)"
138148
- oss/dry_run:
@@ -142,6 +152,7 @@ workflows:
142152
- "JS: Node 12"
143153
- "JS: Node 14"
144154
- "JS: Node 16"
155+
- "JS: Node 17"
145156
- "GraphQL Types (up to date)"
146157
- "Error code Doc (up to date)"
147158
- oss/confirmation:

composition-js/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ This CHANGELOG pertains only to Apollo Federation packages in the 2.x range. The
66

77
> The changes noted within this `vNEXT` section have not been released yet. New PRs and commits which introduce changes should include an entry in this `vNEXT` section as part of their development. When a release is being prepared, a new header will be (manually) created below and the appropriate changes within that release will be moved into the new section.
88
9+
- Adds support for `@inaccessible` in subgraphs [PR #1638](https://github.com/apollographql/federation/pull/1638).
910
- Fix merging of `@tag` directive when it is renamed in subgraphs [PR #1637](https://github.com/apollographql/federation/pull/1637).
1011
- Generates supergraphs with `@link` instead of `@core`. As a result, prior federation 2 pre-release gateway will not read supergraphs generated by this version correctly, so you should upgrade the gateway to this version _before_ re-composing/deploying with this version. [PR #1628](https://github.com/apollographql/federation/pull/1628).
12+
- Support for Node 17.
1113

1214
## v2.0.0-preview.7
1315

composition-js/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"author": "Apollo <[email protected]>",
2222
"license": "SEE LICENSE IN ./LICENSE",
2323
"engines": {
24-
"node": ">=12.13.0 <17.0"
24+
"node": ">=12.13.0 <18.0"
2525
},
2626
"publishConfig": {
2727
"access": "public"

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

+258-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { asFed2SubgraphDocument, assert, buildSchema, buildSubgraph, extractSubgraphsFromSupergraph, isObjectType, ObjectType, printSchema, Schema, ServiceDefinition, Subgraphs } from '@apollo/federation-internals';
1+
import { asFed2SubgraphDocument, assert, buildSchema, buildSubgraph, extractSubgraphsFromSupergraph, FEDERATION2_LINK_WTH_FULL_IMPORTS, isObjectType, ObjectType, printSchema, Schema, ServiceDefinition, Subgraphs } from '@apollo/federation-internals';
22
import { CompositionResult, composeServices, CompositionSuccess } from '../compose';
33
import gql from 'graphql-tag';
44
import './matchers';
5+
import { print } from 'graphql';
56

67
function assertCompositionSuccess(r: CompositionResult): asserts r is CompositionSuccess {
78
if (r.errors) {
@@ -253,7 +254,7 @@ describe('composition', () => {
253254
expect(subgraphs.get('subgraphA')!.toString()).toMatchString(`
254255
schema
255256
@link(url: "https://specs.apollo.dev/link/v1.0")
256-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@extends"])
257+
${FEDERATION2_LINK_WTH_FULL_IMPORTS}
257258
{
258259
query: Query
259260
}
@@ -271,7 +272,7 @@ describe('composition', () => {
271272
expect(subgraphs.get('subgraphB')!.toString()).toMatchString(`
272273
schema
273274
@link(url: "https://specs.apollo.dev/link/v1.0")
274-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@extends"])
275+
${FEDERATION2_LINK_WTH_FULL_IMPORTS}
275276
{
276277
query: Query
277278
}
@@ -327,7 +328,7 @@ describe('composition', () => {
327328
expect(subgraphs.get('subgraphA')!.toString()).toMatchString(`
328329
schema
329330
@link(url: "https://specs.apollo.dev/link/v1.0")
330-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@extends"])
331+
${FEDERATION2_LINK_WTH_FULL_IMPORTS}
331332
{
332333
query: Query
333334
}
@@ -347,7 +348,7 @@ describe('composition', () => {
347348
expect(subgraphs.get('subgraphB')!.toString()).toMatchString(`
348349
schema
349350
@link(url: "https://specs.apollo.dev/link/v1.0")
350-
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@extends"])
351+
${FEDERATION2_LINK_WTH_FULL_IMPORTS}
351352
{
352353
query: Query
353354
}
@@ -2327,4 +2328,256 @@ describe('composition', () => {
23272328
expect(tagOnQ2?.arguments()['name']).toBe('t2');
23282329
})
23292330
});
2331+
2332+
describe('@inaccessible', () => {
2333+
it('propagates @inaccessible to the supergraph', () => {
2334+
const subgraphA = {
2335+
typeDefs: gql`
2336+
type Query {
2337+
me: User @inaccessible
2338+
users: [User]
2339+
}
2340+
2341+
type User @key(fields: "id") {
2342+
id: ID!
2343+
name: String!
2344+
}
2345+
`,
2346+
name: 'subgraphA',
2347+
};
2348+
2349+
const subgraphB = {
2350+
typeDefs: gql`
2351+
type User @key(fields: "id") {
2352+
id: ID!
2353+
birthdate: String!
2354+
age: Int! @inaccessible
2355+
}
2356+
`,
2357+
name: 'subgraphB',
2358+
};
2359+
2360+
const result = composeAsFed2Subgraphs([subgraphA, subgraphB]);
2361+
assertCompositionSuccess(result);
2362+
const supergraph = result.schema;
2363+
expect(supergraph.schemaDefinition.rootType('query')?.field('me')?.appliedDirectivesOf('inaccessible').pop()).toBeDefined();
2364+
2365+
const userType = supergraph.type('User');
2366+
assert(userType && isObjectType(userType), `Should be an object type`);
2367+
expect(userType?.field('age')?.appliedDirectivesOf('inaccessible').pop()).toBeDefined();
2368+
});
2369+
2370+
it('merges @inacessible on the same element', () => {
2371+
const subgraphA = {
2372+
typeDefs: gql`
2373+
type Query {
2374+
user: [User]
2375+
}
2376+
2377+
type User @key(fields: "id") {
2378+
id: ID!
2379+
name: String @shareable @inaccessible
2380+
}
2381+
`,
2382+
name: 'subgraphA',
2383+
};
2384+
2385+
const subgraphB = {
2386+
typeDefs: gql`
2387+
type User @key(fields: "id") {
2388+
id: ID!
2389+
name: String @shareable @inaccessible
2390+
}
2391+
`,
2392+
name: 'subgraphB',
2393+
};
2394+
2395+
const result = composeAsFed2Subgraphs([subgraphA, subgraphB]);
2396+
assertCompositionSuccess(result);
2397+
const supergraph = result.schema;
2398+
2399+
const userType = supergraph.type('User');
2400+
assert(userType && isObjectType(userType), `Should be an object type`);
2401+
expect(userType?.field('name')?.appliedDirectivesOf('inaccessible').pop()).toBeDefined();
2402+
});
2403+
2404+
describe('rejects @inaccessible and @external together', () => {
2405+
const subgraphA = {
2406+
typeDefs: gql`
2407+
type Query {
2408+
user: [User]
2409+
}
2410+
2411+
type User @key(fields: "id") {
2412+
id: ID!
2413+
name: String!
2414+
birthdate: Int! @external @inaccessible
2415+
age: Int! @requires(fields: "birthdate")
2416+
}
2417+
`,
2418+
name: 'subgraphA',
2419+
};
2420+
2421+
const subgraphB = {
2422+
typeDefs: gql`
2423+
type User @key(fields: "id") {
2424+
id: ID!
2425+
birthdate: Int!
2426+
}
2427+
`,
2428+
name: 'subgraphB',
2429+
};
2430+
2431+
const result = composeAsFed2Subgraphs([subgraphA, subgraphB]);
2432+
expect(result.errors).toBeDefined();
2433+
expect(errors(result)).toStrictEqual([
2434+
['MERGED_DIRECTIVE_APPLICATION_ON_EXTERNAL', '[subgraphA] Cannot apply merged directive @inaccessible to external field "User.birthdate"']
2435+
]);
2436+
});
2437+
2438+
it('errors out if @inaccessible is imported under mismatched names', () => {
2439+
const subgraphA = {
2440+
typeDefs: gql`
2441+
extend schema
2442+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: [{name: "@inaccessible", as: "@private"}])
2443+
2444+
type Query {
2445+
q: Int
2446+
q1: Int @private
2447+
}
2448+
`,
2449+
name: 'subgraphA',
2450+
};
2451+
2452+
const subgraphB = {
2453+
typeDefs: gql`
2454+
extend schema
2455+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible"])
2456+
2457+
type Query {
2458+
q2: Int @inaccessible
2459+
}
2460+
`,
2461+
name: 'subgraphB',
2462+
};
2463+
2464+
const result = composeServices([subgraphA, subgraphB]);
2465+
expect(result.errors).toBeDefined();
2466+
expect(errors(result)).toStrictEqual([
2467+
['LINK_IMPORT_NAME_MISMATCH', 'The federation "@inaccessible" directive is imported with mismatched name between subgraphs: it is imported as "@inaccessible" in subgraph "subgraphB" but "@private" in subgraph "subgraphA"']
2468+
]);
2469+
});
2470+
2471+
it('succeeds if @inaccessible is imported under the same non-default name', () => {
2472+
const subgraphA = {
2473+
typeDefs: gql`
2474+
extend schema
2475+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: [{name: "@inaccessible", as: "@private"}])
2476+
2477+
type Query {
2478+
q: Int
2479+
q1: Int @private
2480+
}
2481+
`,
2482+
name: 'subgraphA',
2483+
};
2484+
2485+
const subgraphB = {
2486+
typeDefs: gql`
2487+
extend schema
2488+
@link(url: "https://specs.apollo.dev/federation/v2.0", import: [{name: "@inaccessible", as: "@private"}])
2489+
2490+
type Query {
2491+
q2: Int @private
2492+
}
2493+
`,
2494+
name: 'subgraphB',
2495+
};
2496+
2497+
const result = composeServices([subgraphA, subgraphB]);
2498+
assertCompositionSuccess(result);
2499+
const supergraph = result.schema;
2500+
expect(supergraph.schemaDefinition.rootType('query')?.field('q1')?.appliedDirectivesOf('private').pop()).toBeDefined();
2501+
expect(supergraph.schemaDefinition.rootType('query')?.field('q2')?.appliedDirectivesOf('private').pop()).toBeDefined();
2502+
});
2503+
2504+
it('ignores inaccessible element when validating composition', () => {
2505+
// The following example would _not_ compose if the `z` was not marked inaccessible since it wouldn't be reachable
2506+
// from the `origin` query. So all this test does is double-checking that validation does pass with it marked inaccessible.
2507+
const subgraphA = {
2508+
typeDefs: gql`
2509+
type Query {
2510+
origin: Point
2511+
}
2512+
2513+
type Point @shareable {
2514+
x: Int
2515+
y: Int
2516+
}
2517+
`,
2518+
name: 'subgraphA',
2519+
};
2520+
2521+
const subgraphB = {
2522+
typeDefs: gql`
2523+
type Point @shareable {
2524+
x: Int
2525+
y: Int
2526+
z: Int @inaccessible
2527+
}
2528+
`,
2529+
name: 'subgraphB',
2530+
};
2531+
2532+
const result = composeAsFed2Subgraphs([subgraphA, subgraphB]);
2533+
assertCompositionSuccess(result);
2534+
});
2535+
2536+
it('errors if a subgraph misuse @inaccessible', () => {
2537+
const subgraphA = {
2538+
typeDefs: gql`
2539+
type Query {
2540+
q1: Int
2541+
q2: A
2542+
}
2543+
2544+
type A @shareable {
2545+
x: Int
2546+
y: Int
2547+
}
2548+
`,
2549+
name: 'subgraphA',
2550+
};
2551+
2552+
const subgraphB = {
2553+
typeDefs: gql`
2554+
type A @shareable @inaccessible {
2555+
x: Int
2556+
y: Int
2557+
}
2558+
`,
2559+
name: 'subgraphB',
2560+
};
2561+
2562+
const result = composeAsFed2Subgraphs([subgraphA, subgraphB]);
2563+
expect(result.errors).toBeDefined();
2564+
expect(errors(result)).toStrictEqual([
2565+
['REFERENCED_INACCESSIBLE', 'Field "Query.q2" returns @inaccessible type "A" without being marked @inaccessible itself.']
2566+
]);
2567+
2568+
// Because @inaccessible are thrown by the toAPISchema code and not the merge code directly, let's make sure the include
2569+
// link to the relevant nodes in the subgaphs. Also note that in those test we don't have proper "location" information
2570+
// in the AST nodes (line numbers in particular) because `gql` doesn't populate those, but printing the AST nodes kind of
2571+
// guarantees us that we do get subgraph nodes and not supergraph nodes because supergraph nodes would have @join__*
2572+
// directives and would _not_ have the `@shareable`/`@inacessible` directives.
2573+
const nodes = result.errors![0].nodes!;
2574+
expect(print(nodes[0])).toMatchString('q2: A');
2575+
expect(print(nodes[1])).toMatchString(`
2576+
type A @shareable @inaccessible {
2577+
x: Int
2578+
y: Int
2579+
}`
2580+
);
2581+
})
2582+
});
23302583
});

0 commit comments

Comments
 (0)