Repository: kamilkisiela/graphql-inspector
Pull request #783 (2019-10-31 16:23:10)

Introduce keepClientFields option in validate command


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ecca9503..13dec1af 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,8 @@
 
 ### vNEXT
 
+- **core** **cli** introduce `keepClientFields` flag in `validate` [PR #783](https://github.com/kamilkisiela/graphql-inspector/pull/783)
+
 ### v1.26.0
 
 -- **core**: add rule to ignore description changes [PR #687](https://github.com/kamilkisiela/graphql-inspector/pull/687) [@fabsrc](https://github.com/fabsrc)
diff --git a/docs/essentials/validate.md b/docs/essentials/validate.md
index 25fa076d..9a8a5391 100644
--- a/docs/essentials/validate.md
+++ b/docs/essentials/validate.md
@@ -11,7 +11,7 @@ Validates documents against a schema and looks for deprecated usage.
 Run the following command:
 
     graphql-inspector validate DOCUMENTS SCHEMA
-    
+
 **Example**
 
     graphql-inspector validate './documents/*.graphql' http://localhost:3000/graphql
@@ -26,6 +26,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_)
+- `--keepClientFields <b>` - Keeps the fields with @client, but removes @client directive from them - works only with combination of `--apollo` (default: _false_)
 - `--maxDepth <n>` - Fail when operation depth exceeds maximum depth (default: _false_)
 - `-r, --require 
` - require a module
 - `-t, --token 
` - an access token
diff --git a/example/documents/depth-post.graphql b/example/documents/depth-post.graphql
index 662ef665..2169d540 100644
--- a/example/documents/depth-post.graphql
+++ b/example/documents/depth-post.graphql
@@ -1,7 +1,7 @@
 query depthPost {
   post {
     id
-    title
+    title @client
     author {
       id
       name
diff --git a/packages/cli/src/commands/validate.ts b/packages/cli/src/commands/validate.ts
index e3c3a5be..d3bf6037 100644
--- a/packages/cli/src/commands/validate.ts
+++ b/packages/cli/src/commands/validate.ts
@@ -19,6 +19,7 @@ export async function validate(
     deprecated: boolean;
     noStrictFragments: boolean;
     apollo?: boolean;
+    keepClientFields?: boolean;
     maxDepth?: number;
     renderer?: Renderer;
     headers?: Record<string, string>;
@@ -36,6 +37,7 @@ export async function validate(
       strictFragments: !options.noStrictFragments,
       maxDepth: options.maxDepth || undefined,
       apollo: options.apollo || false,
+      keepClientFields: options.keepClientFields || false,
     });
 
     if (!invalidDocuments.length) {
diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts
index cb7c187e..f510013c 100644
--- a/packages/cli/src/index.ts
+++ b/packages/cli/src/index.ts
@@ -65,6 +65,11 @@ commander
     parseInt(val, 10),
   )
   .option('--apollo', 'Support Apollo directives', false)
+  .option(
+    '--keepClientFields',
+    'Keeps the fields with @client, but removes @client directive from them',
+    false,
+  )
   .description('Validate documents against a schema')
   .action((documents: string, schema: string, cmd: commander.Command) =>
     validate(documents, schema, normalizeOptions(cmd)),
diff --git a/packages/core/src/utils/apollo.ts b/packages/core/src/utils/apollo.ts
index 860b2a10..184512ec 100644
--- a/packages/core/src/utils/apollo.ts
+++ b/packages/core/src/utils/apollo.ts
@@ -1,11 +1,16 @@
 import {DocumentNode, visit, parse, GraphQLSchema, extendSchema} from 'graphql';
 
-import {removeFieldIfDirectives} from './graphql';
+import {removeFieldIfDirectives, removeDirectives} from './graphql';
 
-export function transformDocumentWithApollo(doc: DocumentNode): DocumentNode {
+export function transformDocumentWithApollo(
+  doc: DocumentNode,
+  {keepClientFields}: {keepClientFields: boolean},
+): DocumentNode {
   return visit(doc, {
     Field(node) {
-      return removeFieldIfDirectives(node, ['client']);
+      return keepClientFields
+        ? removeDirectives(node, ['client'])
+        : removeFieldIfDirectives(node, ['client']);
     },
   });
 }
diff --git a/packages/core/src/utils/graphql.ts b/packages/core/src/utils/graphql.ts
index 14c2705c..0d71419c 100644
--- a/packages/core/src/utils/graphql.ts
+++ b/packages/core/src/utils/graphql.ts
@@ -170,3 +170,19 @@ export function removeFieldIfDirectives(
 
   return node;
 }
+
+export function removeDirectives(
+  node: FieldNode,
+  directiveNames: string[],
+): FieldNode {
+  if (node.directives) {
+    return {
+      ...node,
+      directives: node.directives.filter(
+        d => directiveNames.indexOf(d.name.value) === -1,
+      ),
+    };
+  }
+
+  return node;
+}
diff --git a/packages/core/src/validate/index.ts b/packages/core/src/validate/index.ts
index 472093d9..f47c16c1 100644
--- a/packages/core/src/validate/index.ts
+++ b/packages/core/src/validate/index.ts
@@ -25,9 +25,30 @@ export interface InvalidDocument {
 }
 
 export interface ValidateOptions {
+  /**
+   * Fails on duplicated fragment names
+   * @default true
+   */
   strictFragments?: boolean;
+  /**
+   * Fails on deprecated usage
+   * @default true
+   */
   strictDeprecated?: boolean;
+  /**
+   * Works only with combination of `apollo`
+   * @default false
+   */
+  keepClientFields?: boolean;
+  /**
+   * Supports Apollo directives (`@client` and `@connection`)
+   * @default false
+   */
   apollo?: boolean;
+  /**
+   * Fails when operation depth exceeds maximum depth
+   * @default false
+   */
   maxDepth?: number;
 }
 
@@ -39,6 +60,7 @@ export function validate(
   const config: ValidateOptions = {
     strictDeprecated: true,
     strictFragments: true,
+    keepClientFields: false,
     apollo: false,
     ...options,
   };
@@ -99,7 +121,9 @@ export function validate(
         ? transformSchemaWithApollo(schema)
         : schema;
       const transformedDoc = config.apollo
-        ? transformDocumentWithApollo(merged)
+        ? transformDocumentWithApollo(merged, {
+            keepClientFields: config.keepClientFields!,
+          })
         : merged;
 
       const errors =


Test patch:
diff --git a/packages/core/__tests__/validate/apollo.ts b/packages/core/__tests__/validate/apollo.ts
index 709bf236..f48845c1 100644
--- a/packages/core/__tests__/validate/apollo.ts
+++ b/packages/core/__tests__/validate/apollo.ts
@@ -61,4 +61,45 @@ describe('apollo', () => {
 
     expect(results).toHaveLength(0);
   });
+
+  test('should not remove fields with @client directive (on demand)', () => {
+    const schema = buildSchema(/* GraphQL */ `
+      type Query {
+        random(seed: Int): Int!
+      }
+    `);
+
+    const doc = parse(/* GraphQL */ `
+      query Random($seed: Int) {
+        random(seed: $seed) @client
+      }
+    `);
+
+    const results = validate(schema, [new Source(print(doc))], {
+      apollo: true,
+      keepClientFields: true,
+    });
+
+    expect(results).toHaveLength(0);
+  });
+
+  test('should remove fields with @client directive by default', () => {
+    const schema = buildSchema(/* GraphQL */ `
+      type Query {
+        random(seed: Int): Int!
+      }
+    `);
+
+    const doc = parse(/* GraphQL */ `
+      query Random($seed: Int) {
+        random(seed: $seed) @client
+      }
+    `);
+
+    const results = validate(schema, [new Source(print(doc))], {
+      apollo: true,
+    });
+
+    expect(results).toHaveLength(1);
+  });
 });