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

Strip all Type System Directives during composition #3736

Merged
merged 13 commits into from
Jan 31, 2020
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
2 changes: 2 additions & 0 deletions packages/apollo-federation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

> 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 the appropriate changes within that release will be moved into the new section.

* Strip all Type System Directives during composition [#3736](https://github.com/apollographql/apollo-server/pull/3736)

# v0.11.1

> [See complete versioning details.](https://github.com/apollographql/apollo-server/commit/2a4c654986a158aaccf947ee56a4bfc48a3173c7)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,11 +329,11 @@ describe('composition of value types', () => {
`union CatalogItem = Couch | Mattress`,
);
expect(schema.getType('Couch')).toMatchInlineSnapshot(`
type Couch {
sku: ID!
material: String!
}
`);
type Couch {
sku: ID!
material: String!
}
`);
});

it('input types', () => {
Expand All @@ -345,11 +345,11 @@ describe('composition of value types', () => {
`);
expect(errors).toHaveLength(0);
expect(schema.getType('NewProductInput')).toMatchInlineSnapshot(`
input NewProductInput {
sku: ID!
type: String
}
`);
input NewProductInput {
sku: ID!
type: String
}
`);
});

it('interfaces', () => {
Expand All @@ -360,10 +360,10 @@ describe('composition of value types', () => {
`);
expect(errors).toHaveLength(0);
expect(schema.getType('Product')).toMatchInlineSnapshot(`
interface Product {
sku: ID!
}
`);
interface Product {
sku: ID!
}
`);
});

it('enums', () => {
Expand All @@ -375,11 +375,11 @@ describe('composition of value types', () => {
`);
expect(errors).toHaveLength(0);
expect(schema.getType('CatalogItemEnum')).toMatchInlineSnapshot(`
enum CatalogItemEnum {
COUCH
MATTRESS
}
`);
enum CatalogItemEnum {
COUCH
MATTRESS
}
`);
});
});

Expand Down Expand Up @@ -551,3 +551,76 @@ describe('composition of value types', () => {
});
});
});

describe('composition of schemas with directives', () => {
/**
* To see which usage sites indicate whether a directive is "executable" or
* merely for use by the type-system ("type-system"), see the GraphQL spec:
* https://graphql.github.io/graphql-spec/June2018/#sec-Type-System.Directives
*/
it('preserves executable and purges type-system directives', () => {
const serviceA = {
typeDefs: gql`
"directives at FIELDs are executable"
directive @audit(risk: Int!) on FIELD

"directives at FIELD_DEFINITIONs are for the type-system"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that @deprecated defies this semantic: https://www.apollographql.com/docs/graphql-tools/schema-directives/

directive @transparency(concealment: Int!) on FIELD_DEFINITION

type EarthConcern {
environmental: String! @transparency(concealment: 5)
}

extend type Query {
importantDirectives: [EarthConcern!]!
}
`,
name: 'serviceA',
};

const serviceB = {
typeDefs: gql`
"directives at FIELDs are executable"
directive @audit(risk: Int!) on FIELD

"directives at FIELD_DEFINITIONs are for the type-system"
directive @transparency(concealment: Int!) on FIELD_DEFINITION

"directives at OBJECTs are for the type-system"
directive @experimental on OBJECT

extend type EarthConcern @experimental {
societal: String! @transparency(concealment: 6)
}
`,
name: 'serviceB',
};

const { schema, errors } = composeAndValidate([serviceA, serviceB]);
expect(errors).toHaveLength(0);

const audit = schema.getDirective('audit');
expect(audit).toMatchInlineSnapshot(`"@audit"`);

const transparency = schema.getDirective('transparency');
expect(transparency).toBeUndefined();

const type = schema.getType('EarthConcern') as GraphQLObjectType;

expect(type.astNode).toMatchInlineSnapshot(`
type EarthConcern {
environmental: String!
}
`);

const fields = type.getFields();

expect(fields['environmental'].astNode).toMatchInlineSnapshot(
`environmental: String!`,
);

expect(fields['societal'].astNode).toMatchInlineSnapshot(
`societal: String!`,
);
});
});
10 changes: 9 additions & 1 deletion packages/apollo-federation/src/composition/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
mapValues,
isFederationDirective,
executableDirectiveLocations,
stripTypeSystemDirectivesFromTypeDefs,
} from './utils';
import {
ServiceDefinition,
Expand Down Expand Up @@ -135,7 +136,14 @@ export function buildMapsFromServiceList(serviceList: ServiceDefinition[]) {

externalFields.push(...strippedFields);

for (let definition of typeDefsWithoutExternalFields.definitions) {
// Type system directives from downstream services are not a concern of the
// gateway, but rather the services on which the fields live which serve
// those types. In other words, its up to an implementing service to
// act on such directives, not the gateway.
const typeDefsWithoutTypeSystemDirectives =
stripTypeSystemDirectivesFromTypeDefs(typeDefsWithoutExternalFields);

for (const definition of typeDefsWithoutTypeSystemDirectives.definitions) {
if (
definition.kind === Kind.OBJECT_TYPE_DEFINITION ||
definition.kind === Kind.OBJECT_TYPE_EXTENSION
Expand Down
14 changes: 14 additions & 0 deletions packages/apollo-federation/src/composition/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,20 @@ export function stripExternalFieldsFromTypeDefs(
return { typeDefsWithoutExternalFields, strippedFields };
}

export function stripTypeSystemDirectivesFromTypeDefs(typeDefs: DocumentNode) {
const typeDefsWithoutTypeSystemDirectives = visit(typeDefs, {
Directive(node) {
const isFederationDirective = federationDirectives.some(
({ name }) => name === node.name.value,
);
// Returning `null` to a visit will cause it to be removed from the tree.
return isFederationDirective ? undefined : null;
},
}) as DocumentNode;

return typeDefsWithoutTypeSystemDirectives;
}

/**
* Returns a closure that strips fields marked with `@external` and adds them
* to an array.
Expand Down