Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allows @inacessible in subgraphs #1638

Merged
merged 3 commits into from
Mar 25, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
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
}`
);
})
});
});
16 changes: 8 additions & 8 deletions composition-js/src/__tests__/composeFed1Subgraphs.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { buildSchema, extractSubgraphsFromSupergraph, printSchema, Schema, SubgraphASTNode, Subgraphs } from '@apollo/federation-internals';
import { buildSchema, extractSubgraphsFromSupergraph, FEDERATION2_LINK_WTH_FULL_IMPORTS, printSchema, Schema, SubgraphASTNode, Subgraphs } from '@apollo/federation-internals';
import { CompositionResult, composeServices, CompositionSuccess } from '../compose';
import gql from 'graphql-tag';
import './matchers';
Expand Down Expand Up @@ -67,7 +67,7 @@ describe('basic type extensions', () => {
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 @@ -90,7 +90,7 @@ describe('basic type extensions', () => {
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 @@ -151,7 +151,7 @@ describe('basic type extensions', () => {
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 @@ -167,7 +167,7 @@ describe('basic type extensions', () => {
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 @@ -240,7 +240,7 @@ describe('basic type extensions', () => {
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 @@ -256,7 +256,7 @@ describe('basic type extensions', () => {
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 All @@ -274,7 +274,7 @@ describe('basic type extensions', () => {
expect(subgraphs.get('subgraphC')!.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
Loading