Skip to content

Commit

Permalink
Merge branch 'main' into weatherman/aggregate-invalid-inaccessible
Browse files Browse the repository at this point in the history
  • Loading branch information
benweatherman committed Mar 28, 2022
2 parents 5cb1d6c + 41ccc43 commit 711baef
Show file tree
Hide file tree
Showing 31 changed files with 552 additions and 64 deletions.
11 changes: 11 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ jobs:
steps:
- common_test_steps

NodeJS 17:
docker:
- image: cimg/node:17.0.0
steps:
- common_test_steps

GraphQL Types:
description: "Assert generated GraphQL types are up to date"
docker:
Expand Down Expand Up @@ -120,6 +126,9 @@ workflows:
- NodeJS 16:
name: "JS: Node 16"
<<: *common_non_publish_filters
- NodeJS 17:
name: "JS: Node 17"
<<: *common_non_publish_filters
- GraphQL Types:
name: "GraphQL Types (up to date)"
<<: *common_non_publish_filters
Expand All @@ -133,6 +142,7 @@ workflows:
- "JS: Node 12"
- "JS: Node 14"
- "JS: Node 16"
- "JS: Node 17"
- "GraphQL Types (up to date)"
- "Error code Doc (up to date)"
- oss/dry_run:
Expand All @@ -142,6 +152,7 @@ workflows:
- "JS: Node 12"
- "JS: Node 14"
- "JS: Node 16"
- "JS: Node 17"
- "GraphQL Types (up to date)"
- "Error code Doc (up to date)"
- oss/confirmation:
Expand Down
2 changes: 2 additions & 0 deletions composition-js/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ This CHANGELOG pertains only to Apollo Federation packages in the 2.x range. The

> 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.
- Adds support for `@inaccessible` in subgraphs [PR #1638](https://github.com/apollographql/federation/pull/1638).
- Fix merging of `@tag` directive when it is renamed in subgraphs [PR #1637](https://github.com/apollographql/federation/pull/1637).
- 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).
- Support for Node 17.

## v2.0.0-preview.7

Expand Down
2 changes: 1 addition & 1 deletion composition-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"author": "Apollo <packages@apollographql.com>",
"license": "SEE LICENSE IN ./LICENSE",
"engines": {
"node": ">=12.13.0 <17.0"
"node": ">=12.13.0 <18.0"
},
"publishConfig": {
"access": "public"
Expand Down
263 changes: 258 additions & 5 deletions composition-js/src/__tests__/compose.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { asFed2SubgraphDocument, assert, buildSchema, buildSubgraph, extractSubgraphsFromSupergraph, isObjectType, ObjectType, printSchema, Schema, ServiceDefinition, Subgraphs } from '@apollo/federation-internals';
import { asFed2SubgraphDocument, assert, buildSchema, buildSubgraph, extractSubgraphsFromSupergraph, FEDERATION2_LINK_WTH_FULL_IMPORTS, isObjectType, ObjectType, printSchema, Schema, ServiceDefinition, Subgraphs } from '@apollo/federation-internals';
import { CompositionResult, composeServices, CompositionSuccess } from '../compose';
import gql from 'graphql-tag';
import './matchers';
import { print } from 'graphql';

function assertCompositionSuccess(r: CompositionResult): asserts r is CompositionSuccess {
if (r.errors) {
Expand Down Expand Up @@ -253,7 +254,7 @@ describe('composition', () => {
expect(subgraphs.get('subgraphA')!.toString()).toMatchString(`
schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@extends"])
${FEDERATION2_LINK_WTH_FULL_IMPORTS}
{
query: Query
}
Expand All @@ -271,7 +272,7 @@ describe('composition', () => {
expect(subgraphs.get('subgraphB')!.toString()).toMatchString(`
schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@extends"])
${FEDERATION2_LINK_WTH_FULL_IMPORTS}
{
query: Query
}
Expand Down Expand Up @@ -327,7 +328,7 @@ describe('composition', () => {
expect(subgraphs.get('subgraphA')!.toString()).toMatchString(`
schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@extends"])
${FEDERATION2_LINK_WTH_FULL_IMPORTS}
{
query: Query
}
Expand All @@ -347,7 +348,7 @@ describe('composition', () => {
expect(subgraphs.get('subgraphB')!.toString()).toMatchString(`
schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@requires", "@provides", "@external", "@shareable", "@tag", "@extends"])
${FEDERATION2_LINK_WTH_FULL_IMPORTS}
{
query: Query
}
Expand Down Expand Up @@ -2327,4 +2328,256 @@ describe('composition', () => {
expect(tagOnQ2?.arguments()['name']).toBe('t2');
})
});

describe('@inaccessible', () => {
it('propagates @inaccessible to the supergraph', () => {
const subgraphA = {
typeDefs: gql`
type Query {
me: User @inaccessible
users: [User]
}
type User @key(fields: "id") {
id: ID!
name: String!
}
`,
name: 'subgraphA',
};

const subgraphB = {
typeDefs: gql`
type User @key(fields: "id") {
id: ID!
birthdate: String!
age: Int! @inaccessible
}
`,
name: 'subgraphB',
};

const result = composeAsFed2Subgraphs([subgraphA, subgraphB]);
assertCompositionSuccess(result);
const supergraph = result.schema;
expect(supergraph.schemaDefinition.rootType('query')?.field('me')?.appliedDirectivesOf('inaccessible').pop()).toBeDefined();

const userType = supergraph.type('User');
assert(userType && isObjectType(userType), `Should be an object type`);
expect(userType?.field('age')?.appliedDirectivesOf('inaccessible').pop()).toBeDefined();
});

it('merges @inacessible on the same element', () => {
const subgraphA = {
typeDefs: gql`
type Query {
user: [User]
}
type User @key(fields: "id") {
id: ID!
name: String @shareable @inaccessible
}
`,
name: 'subgraphA',
};

const subgraphB = {
typeDefs: gql`
type User @key(fields: "id") {
id: ID!
name: String @shareable @inaccessible
}
`,
name: 'subgraphB',
};

const result = composeAsFed2Subgraphs([subgraphA, subgraphB]);
assertCompositionSuccess(result);
const supergraph = result.schema;

const userType = supergraph.type('User');
assert(userType && isObjectType(userType), `Should be an object type`);
expect(userType?.field('name')?.appliedDirectivesOf('inaccessible').pop()).toBeDefined();
});

describe('rejects @inaccessible and @external together', () => {
const subgraphA = {
typeDefs: gql`
type Query {
user: [User]
}
type User @key(fields: "id") {
id: ID!
name: String!
birthdate: Int! @external @inaccessible
age: Int! @requires(fields: "birthdate")
}
`,
name: 'subgraphA',
};

const subgraphB = {
typeDefs: gql`
type User @key(fields: "id") {
id: ID!
birthdate: Int!
}
`,
name: 'subgraphB',
};

const result = composeAsFed2Subgraphs([subgraphA, subgraphB]);
expect(result.errors).toBeDefined();
expect(errors(result)).toStrictEqual([
['MERGED_DIRECTIVE_APPLICATION_ON_EXTERNAL', '[subgraphA] Cannot apply merged directive @inaccessible to external field "User.birthdate"']
]);
});

it('errors out if @inaccessible is imported under mismatched names', () => {
const subgraphA = {
typeDefs: gql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0", import: [{name: "@inaccessible", as: "@private"}])
type Query {
q: Int
q1: Int @private
}
`,
name: 'subgraphA',
};

const subgraphB = {
typeDefs: gql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@inaccessible"])
type Query {
q2: Int @inaccessible
}
`,
name: 'subgraphB',
};

const result = composeServices([subgraphA, subgraphB]);
expect(result.errors).toBeDefined();
expect(errors(result)).toStrictEqual([
['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"']
]);
});

it('succeeds if @inaccessible is imported under the same non-default name', () => {
const subgraphA = {
typeDefs: gql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0", import: [{name: "@inaccessible", as: "@private"}])
type Query {
q: Int
q1: Int @private
}
`,
name: 'subgraphA',
};

const subgraphB = {
typeDefs: gql`
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0", import: [{name: "@inaccessible", as: "@private"}])
type Query {
q2: Int @private
}
`,
name: 'subgraphB',
};

const result = composeServices([subgraphA, subgraphB]);
assertCompositionSuccess(result);
const supergraph = result.schema;
expect(supergraph.schemaDefinition.rootType('query')?.field('q1')?.appliedDirectivesOf('private').pop()).toBeDefined();
expect(supergraph.schemaDefinition.rootType('query')?.field('q2')?.appliedDirectivesOf('private').pop()).toBeDefined();
});

it('ignores inaccessible element when validating composition', () => {
// The following example would _not_ compose if the `z` was not marked inaccessible since it wouldn't be reachable
// from the `origin` query. So all this test does is double-checking that validation does pass with it marked inaccessible.
const subgraphA = {
typeDefs: gql`
type Query {
origin: Point
}
type Point @shareable {
x: Int
y: Int
}
`,
name: 'subgraphA',
};

const subgraphB = {
typeDefs: gql`
type Point @shareable {
x: Int
y: Int
z: Int @inaccessible
}
`,
name: 'subgraphB',
};

const result = composeAsFed2Subgraphs([subgraphA, subgraphB]);
assertCompositionSuccess(result);
});

it('errors if a subgraph misuse @inaccessible', () => {
const subgraphA = {
typeDefs: gql`
type Query {
q1: Int
q2: A
}
type A @shareable {
x: Int
y: Int
}
`,
name: 'subgraphA',
};

const subgraphB = {
typeDefs: gql`
type A @shareable @inaccessible {
x: Int
y: Int
}
`,
name: 'subgraphB',
};

const result = composeAsFed2Subgraphs([subgraphA, subgraphB]);
expect(result.errors).toBeDefined();
expect(errors(result)).toStrictEqual([
['REFERENCED_INACCESSIBLE', 'Field "Query.q2" returns @inaccessible type "A" without being marked @inaccessible itself.']
]);

// Because @inaccessible are thrown by the toAPISchema code and not the merge code directly, let's make sure the include
// link to the relevant nodes in the subgaphs. Also note that in those test we don't have proper "location" information
// in the AST nodes (line numbers in particular) because `gql` doesn't populate those, but printing the AST nodes kind of
// guarantees us that we do get subgraph nodes and not supergraph nodes because supergraph nodes would have @join__*
// directives and would _not_ have the `@shareable`/`@inacessible` directives.
const nodes = result.errors![0].nodes!;
expect(print(nodes[0])).toMatchString('q2: A');
expect(print(nodes[1])).toMatchString(`
type A @shareable @inaccessible {
x: Int
y: Int
}`
);
})
});
});
Loading

0 comments on commit 711baef

Please sign in to comment.