Repository: FormidableLabs/urql
Pull request #1829 (2021-07-30 07:57:21)

(core) - error for invalid operation passed to query/mutation/subscription
Fixes https://github.com/FormidableLabs/urql/issues/1827

## Summary

Errors out when an invalid operation-node is passed into our client-methods


## Set of changes

 - throw an error when passing in the wrong operation-type to the urql-client

changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: 5ec48ffaa226ac7763c741b0bbd4b381c5b0289e

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name       | Type  |
| ---------- | ----- |
| @urql/core | Minor |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/FormidableLabs/urql/new/warn-for-invalid-operation?filename=.changeset/cold-spoons-marry.md&value=---%0A%22%40urql%2Fcore%22%3A%20patch%0A---%0A%0A(core)%20-%20error%20for%20invalid%20operation%20passed%20to%20query%2Fmutation%2Fsubscription%0A)



Patch:
diff --git a/.changeset/weak-doors-cheer.md b/.changeset/weak-doors-cheer.md
new file mode 100644
index 00000000..60fb2aee
--- /dev/null
+++ b/.changeset/weak-doors-cheer.md
@@ -0,0 +1,5 @@
+---
+'@urql/core': patch
+---
+
+Warn for invalid operation passed to query/subscription/mutation
diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts
index 754035f2..e954b0ab 100755
--- a/packages/core/src/client.ts
+++ b/packages/core/src/client.ts
@@ -46,6 +46,7 @@ import {
   maskTypename,
   noop,
   makeOperation,
+  getOperationType,
 } from './utils';
 
 /** Options for configuring the URQL [client]{@link Client}. */
@@ -289,6 +290,16 @@ export const Client: new (opts: ClientOptions) => Client = function Client(
     },
 
     createRequestOperation(kind, request, opts) {
+      const requestOperationType = getOperationType(request.query);
+      if (
+        process.env.NODE_ENV !== 'production' &&
+        kind !== 'teardown' &&
+        requestOperationType !== kind
+      ) {
+        throw new Error(
+          `Expected operation of type "${kind}" but found "${requestOperationType}"`
+        );
+      }
       return makeOperation(kind, request, client.createOperationContext(opts));
     },
 
diff --git a/packages/core/src/utils/request.ts b/packages/core/src/utils/request.ts
index 7b85daf7..7fdb8c65 100644
--- a/packages/core/src/utils/request.ts
+++ b/packages/core/src/utils/request.ts
@@ -98,3 +98,15 @@ export const getOperationName = (query: DocumentNode): string | undefined => {
     }
   }
 };
+
+/**
+ * Finds the operation-type
+ */
+export const getOperationType = (query: DocumentNode): string | undefined => {
+  for (let i = 0, l = query.definitions.length; i < l; i++) {
+    const node = query.definitions[i];
+    if (node.kind === Kind.OPERATION_DEFINITION) {
+      return node.operation;
+    }
+  }
+};


Test patch:
diff --git a/packages/core/src/client.test.ts b/packages/core/src/client.test.ts
index 98df50bd..57f40fe5 100755
--- a/packages/core/src/client.test.ts
+++ b/packages/core/src/client.test.ts
@@ -52,6 +52,30 @@ const query = {
   variables: { example: 1234 },
 };
 
+const mutation = {
+  key: 1,
+  query: gql`
+    mutation {
+      todos {
+        id
+      }
+    }
+  `,
+  variables: { example: 1234 },
+};
+
+const subscription = {
+  key: 1,
+  query: gql`
+    subscription {
+      todos {
+        id
+      }
+    }
+  `,
+  variables: { example: 1234 },
+};
+
 let receivedOps: Operation[] = [];
 let client = createClient({ url: '1234' });
 const receiveMock = jest.fn((s: Source<Operation>) =>
@@ -119,21 +143,17 @@ describe('promisified methods', () => {
   });
 
   it('mutation', () => {
-    const mutationResult = client
-      .mutation(
-        gql`
-          {
-            todos {
-              id
-            }
-          }
-        `,
-        { example: 1234 }
-      )
-      .toPromise();
+    const mut = gql`
+      mutation {
+        todos {
+          id
+        }
+      }
+    `;
+    const mutationResult = client.mutation(mut, { example: 1234 }).toPromise();
 
     const received = receivedOps[0];
-    expect(print(received.query)).toEqual(print(query.query));
+    expect(print(received.query)).toEqual(print(mut));
     expect(received.key).toBeDefined();
     expect(received.variables).toEqual({ example: 1234 });
     expect(received.kind).toEqual('mutation');
@@ -187,6 +207,17 @@ describe('executeQuery', () => {
     expect(print(receivedQuery)).toBe(print(query.query));
   });
 
+  it('should throw when passing in a mutation', () => {
+    try {
+      client.executeQuery(mutation);
+      expect(true).toBeFalsy();
+    } catch (e) {
+      expect(e.message).toMatchInlineSnapshot(
+        `"Expected operation of type \\"query\\" but found \\"mutation\\""`
+      );
+    }
+  });
+
   it('passes variables type to exchange', () => {
     pipe(
       client.executeQuery(query),
@@ -242,17 +273,17 @@ describe('executeQuery', () => {
 describe('executeMutation', () => {
   it('passes query string exchange', async () => {
     pipe(
-      client.executeMutation(query),
+      client.executeMutation(mutation),
       subscribe(x => x)
     );
 
     const receivedQuery = receivedOps[0].query;
-    expect(print(receivedQuery)).toBe(print(query.query));
+    expect(print(receivedQuery)).toBe(print(mutation.query));
   });
 
   it('passes variables type to exchange', () => {
     pipe(
-      client.executeMutation(query),
+      client.executeMutation(mutation),
       subscribe(x => x)
     );
 
@@ -261,7 +292,7 @@ describe('executeMutation', () => {
 
   it('passes kind type to exchange', () => {
     pipe(
-      client.executeMutation(query),
+      client.executeMutation(mutation),
       subscribe(x => x)
     );
 
@@ -270,7 +301,7 @@ describe('executeMutation', () => {
 
   it('passes url (from context) to exchange', () => {
     pipe(
-      client.executeMutation(query),
+      client.executeMutation(mutation),
       subscribe(x => x)
     );
 
@@ -281,26 +312,26 @@ describe('executeMutation', () => {
 describe('executeSubscription', () => {
   it('passes query string exchange', async () => {
     pipe(
-      client.executeSubscription(query),
+      client.executeSubscription(subscription),
       subscribe(x => x)
     );
 
     const receivedQuery = receivedOps[0].query;
-    expect(print(receivedQuery)).toBe(print(query.query));
+    expect(print(receivedQuery)).toBe(print(subscription.query));
   });
 
   it('passes variables type to exchange', () => {
     pipe(
-      client.executeSubscription(query),
+      client.executeSubscription(subscription),
       subscribe(x => x)
     );
 
-    expect(receivedOps[0]).toHaveProperty('variables', query.variables);
+    expect(receivedOps[0]).toHaveProperty('variables', subscription.variables);
   });
 
   it('passes kind type to exchange', () => {
     pipe(
-      client.executeSubscription(query),
+      client.executeSubscription(subscription),
       subscribe(x => x)
     );