Repository: FormidableLabs/urql
Pull request #2435 (2022-05-08 05:50:37)

fix(execute-exchange): prune undefined variables


<!--
  Thanks for opening a pull request! We appreciate your dedication and help!
  Before submitting your pull request, please make sure to read our CONTRIBUTING guide.

  The best contribution is always a PR! However, if you're starting to work on a large
  change, it's best to make sure to open an issue first.

  If this PR is already related to an issue, please reference it like so:
  Resolves #123
-->

## Summary

This allows for directives to correctly validate with variables using default values.
```graphql
query($count: Boolean = false) {
  users {
    # Fails if $count is not provided:
    # Argument "if" of non-null type "Boolean!" must not be null.
    count @include(if: $count)
    list { id name }
  }
}
```

## Set of changes

<!--
  Roughly list the changes you've made and which packages are affected.
  Leave some notes on what may be noteworthy files you've changed.
  And lastly, please let us know if you think this is a breaking change.
-->

- `exchanges/execute` (`@urql/execute-exchange`): 
	- `src/execute.ts`/`executeExchange()`: filter `undefined` variables before passing them to `graphql.execute()`


changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: 924abfdf2d8152b1616f5f037658d06144518c46

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/refloat-ci/urql/new/fix-execute-directives?filename=.changeset/violet-carpets-bow.md&value=---%0A%22%40urql%2Fexchange-execute%22%3A%20patch%0A---%0A%0Afix(execute-exchange)%3A%20prune%20undefined%20variables%0A)



Patch:
diff --git a/.changeset/shiny-bags-serve.md b/.changeset/shiny-bags-serve.md
new file mode 100644
index 00000000..8fdf6875
--- /dev/null
+++ b/.changeset/shiny-bags-serve.md
@@ -0,0 +1,5 @@
+---
+'@urql/exchange-execute': patch
+---
+
+Support using default values with directives. Previously, using a variables with a default value within a directive would fail the validation if it is empty.
diff --git a/exchanges/execute/src/execute.ts b/exchanges/execute/src/execute.ts
index 688fdf5e..0a42cdd7 100644
--- a/exchanges/execute/src/execute.ts
+++ b/exchanges/execute/src/execute.ts
@@ -141,13 +141,25 @@ export const executeExchange = ({
 
         const contextValue =
           typeof context === 'function' ? context(operation) : context;
+
+        // Filter undefined values from variables before calling execute()
+        // to support default values within directives.
+        const variableValues = Object.create(null);
+        if (operation.variables) {
+          for (const key in operation.variables) {
+            if (operation.variables[key] !== undefined) {
+              variableValues[key] = operation.variables[key];
+            }
+          }
+        }
+
         return pipe(
           makeExecuteSource(operation, {
             schema,
             document: operation.query,
             rootValue,
             contextValue,
-            variableValues: operation.variables,
+            variableValues,
             operationName: getOperationName(operation.query),
             fieldResolver,
             typeResolver,
diff --git a/exchanges/graphcache/default-storage/package.json b/exchanges/graphcache/default-storage/package.json
index f17d0459..e817e5d1 100644
--- a/exchanges/graphcache/default-storage/package.json
+++ b/exchanges/graphcache/default-storage/package.json
@@ -16,7 +16,7 @@
     "./package.json": "./package.json"
   },
   "dependencies": {
-    "@urql/core": ">=2.3.3",
+    "@urql/core": ">=2.3.6",
     "wonka": "^4.0.14"
   }
 }


Test patch:
diff --git a/exchanges/execute/src/execute.test.ts b/exchanges/execute/src/execute.test.ts
index 19150c59..ff83cd70 100644
--- a/exchanges/execute/src/execute.test.ts
+++ b/exchanges/execute/src/execute.test.ts
@@ -22,7 +22,11 @@ import {
   empty,
   Source,
 } from 'wonka';
-import { queryOperation, subscriptionOperation } from '@urql/core/test-utils';
+import {
+  context,
+  queryOperation,
+  subscriptionOperation,
+} from '@urql/core/test-utils';
 import {
   makeErrorResult,
   makeOperation,
@@ -182,6 +186,45 @@ describe('on operation', () => {
     expect(mocked(execute)).toBeCalledTimes(1);
     expect(fetchMock).toBeCalledTimes(1);
   });
+
+  it('should trim undefined values before calling execute()', async () => {
+    const contextValue = 'USER_ID=123';
+
+    const operation = makeOperation(
+      'query',
+      {
+        ...queryOperation,
+        variables: { ...queryOperation.variables, withLastName: undefined },
+      },
+      context
+    );
+
+    await pipe(
+      fromValue(operation),
+      executeExchange({ schema, context: contextValue })(exchangeArgs),
+      take(1),
+      toPromise
+    );
+
+    expect(mocked(execute)).toBeCalledTimes(1);
+    expect(mocked(execute)).toBeCalledWith({
+      schema,
+      document: queryOperation.query,
+      rootValue: undefined,
+      contextValue: contextValue,
+      variableValues: queryOperation.variables,
+      operationName: expectedQueryOperationName,
+      fieldResolver: undefined,
+      typeResolver: undefined,
+      subscribeFieldResolver: undefined,
+    });
+
+    const variables = mocked(execute).mock.calls[0][0].variableValues;
+
+    for (const key in variables) {
+      expect(variables[key]).not.toBeUndefined();
+    }
+  });
 });
 
 describe('on success response', () => {