Skip to content

Commit

Permalink
Support Apollo directives (#505)
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilkisiela authored Aug 9, 2019
1 parent 5c48e98 commit 8ef9902
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 3 deletions.
1 change: 1 addition & 0 deletions docs/essentials/validate.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Run the following command:

- `-d, --deprecated` - Fail on deprecated usage (default: _false_)
- `--noStrictFragments` - Do not fail on duplicated fragment names (default: _false_)
- `--apollo` - Support Apollo directives (@client and @connection) (default: _false_)
- `--maxDepth <n>` - Fail when operation depth exceeds maximum depth (default: _false_)
- `-r, --require <s>` - require a module
- `-t, --token <s>` - an access token
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/commands/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export async function validate(
require?: string[];
deprecated: boolean;
noStrictFragments: boolean;
apollo?: boolean;
maxDepth?: number;
renderer?: Renderer;
headers?: Record<string, string>;
Expand All @@ -34,6 +35,7 @@ export async function validate(
const invalidDocuments = validateDocuments(schema, documents, {
strictFragments: !options.noStrictFragments,
maxDepth: options.maxDepth || undefined,
apollo: options.apollo || false,
});

if (!invalidDocuments.length) {
Expand Down
1 change: 1 addition & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ commander
.option('--maxDepth <n>', 'Fail on deep operations', (val: string) =>
parseInt(val, 10),
)
.option('--apollo', 'Support Apollo directives', false)
.description('Validate documents against a schema')
.action((documents: string, schema: string, cmd: commander.Command) =>
validate(documents, schema, normalizeOptions(cmd)),
Expand Down
64 changes: 64 additions & 0 deletions packages/core/__tests__/validate/apollo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {buildSchema, Source, print, parse} from 'graphql';

import {validate} from '../../src/validate';

describe('apollo', () => {
test('should remove a filed with @client', () => {
const schema = buildSchema(/* GraphQL */ `
type Post {
id: ID
title: String
}
type Query {
post: Post
}
`);

const doc = parse(/* GraphQL */ `
query getPost {
post {
id
title
extra @client
}
}
`);

const results = validate(schema, [new Source(print(doc))], {
apollo: true,
});

expect(results).toHaveLength(0);
});

test('should include @connection', () => {
const schema = buildSchema(/* GraphQL */ `
type Post {
id: ID
title: String
comments: [String]
}
type Query {
post(id: ID!): Post
}
`);

const doc = parse(/* GraphQL */ `
query getPost {
post(id: 1) {
id
title
comments @connection(key: "comments")
}
}
`);

const results = validate(schema, [new Source(print(doc))], {
apollo: true,
});

expect(results).toHaveLength(0);
});
});
22 changes: 22 additions & 0 deletions packages/core/src/utils/apollo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {DocumentNode, visit, parse, GraphQLSchema, extendSchema} from 'graphql';

import {removeFieldIfDirectives} from './graphql';

export function transformDocumentWithApollo(doc: DocumentNode): DocumentNode {
return visit(doc, {
Field(node) {
return removeFieldIfDirectives(node, ['client']);
},
});
}

export function transformSchemaWithApollo(
schema: GraphQLSchema,
): GraphQLSchema {
return extendSchema(
schema,
parse(/* GraphQL */ `
directive @connection(key: String!, filter: [String]) on FIELD
`),
);
}
16 changes: 16 additions & 0 deletions packages/core/src/utils/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
visit,
visitWithTypeInfo,
getNamedType,
FieldNode,
} from 'graphql';

export function safeChangeForField(
Expand Down Expand Up @@ -154,3 +155,18 @@ export function findDeprecatedUsages(

return errors;
}

export function removeFieldIfDirectives(
node: FieldNode,
directiveNames: string[],
): FieldNode | null {
if (node.directives) {
if (
node.directives.some(d => directiveNames.indexOf(d.name.value) !== -1)
) {
return null;
}
}

return node;
}
23 changes: 20 additions & 3 deletions packages/core/src/validate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import {DepGraph} from 'dependency-graph';
import {readDocument} from '../ast/document';
import {findDeprecatedUsages} from '../utils/graphql';
import {validateQueryDepth} from './query-depth';
import {
transformSchemaWithApollo,
transformDocumentWithApollo,
} from '../utils/apollo';

export interface InvalidDocument {
source: Source;
Expand All @@ -23,6 +27,7 @@ export interface InvalidDocument {
export interface ValidateOptions {
strictFragments?: boolean;
strictDeprecated?: boolean;
apollo?: boolean;
maxDepth?: number;
}

Expand All @@ -34,6 +39,7 @@ export function validate(
const config: ValidateOptions = {
strictDeprecated: true,
strictFragments: true,
apollo: false,
...options,
};
const invalidDocuments: InvalidDocument[] = [];
Expand Down Expand Up @@ -89,12 +95,23 @@ export function validate(
definitions: [...docWithOperations.definitions, ...extractedFragments],
};

const errors = (validateDocument(schema, merged) as GraphQLError[]) || [];
const transformedSchema = config.apollo
? transformSchemaWithApollo(schema)
: schema;
const transformedDoc = config.apollo
? transformDocumentWithApollo(merged)
: merged;

const errors =
(validateDocument(
transformedSchema,
transformedDoc,
) as GraphQLError[]) || [];

if (config.maxDepth) {
const depthError = validateQueryDepth({
source: doc.source,
doc: merged,
doc: transformedDoc,
maxDepth: config.maxDepth,
fragmentGraph: graph,
});
Expand All @@ -105,7 +122,7 @@ export function validate(
}

const deprecated = config.strictDeprecated
? findDeprecatedUsages(schema, parse(doc.source.body))
? findDeprecatedUsages(transformedSchema, parse(doc.source.body))
: [];
const duplicatedFragments = config.strictFragments
? findDuplicatedFragments(fragmentNames)
Expand Down

0 comments on commit 8ef9902

Please sign in to comment.