Repository: FormidableLabs/urql (ts)

Issue:
originalError is not fully preserved during ssr serialization/deserialization
Only `GraphQLError.message` seems to be preserved. See [ssr.ts#40](https://github.com/FormidableLabs/urql/blob/master/packages/core/src/exchanges/ssr.ts#L40)

 In our project (which uses next.js) we'd like to use the `originalError` to access error codes and set a status (e.g. 404 if `error.graphQLErrors[0].originalError.error_type === "NotFound"` in our case. The `originalError` seems to be fully available during the prepass but only `message` is available when next.js performs its own render.

I was able to make a fix for this problem myself in a fork and all seems to be working fine. I haven't made a PR as I need to check out your contribution guidelines first and the solution is probably a bit rough but, none the less, I'd appreciate some feedback/comments on both the problem and my approach to solving it: https://github.com/mtford90/urql/commit/d0229ce815a679c6a33cbaaa4a12295842803bbc

Cheers & let me know if anymore info is needed.


Pull request:
(core) - Fix ssrExchange not serializing extensions or path on GraphQLError
Resolves #562

## Summary

This adds the missing serialization for `GraphQLError`s to the `ssrExchange` when the `GraphQLError` carries the `extensions` or `path` fields. These fields should be preserved even after server-side rendering so that it's possible to pass on metadata from the server-side to the client-side.

The `extensions` field is often used to carry error code or types.

## Set of changes

- Add conditional `GraphQLError` serialization to `ssrExchange`
- Allow `Partial<GraphQLError>` as input for `CombinedError`


Patch:
diff --git a/.changeset/green-owls-pump.md b/.changeset/green-owls-pump.md
new file mode 100644
index 00000000..649b5843
--- /dev/null
+++ b/.changeset/green-owls-pump.md
@@ -0,0 +1,6 @@
+---
+'@urql/core': patch
+'next-urql': patch
+---
+
+Add missing GraphQLError serialization for extensions and path field to ssrExchange
diff --git a/packages/core/src/exchanges/ssr.ts b/packages/core/src/exchanges/ssr.ts
index ccb406a5..65e49ea6 100644
--- a/packages/core/src/exchanges/ssr.ts
+++ b/packages/core/src/exchanges/ssr.ts
@@ -1,3 +1,4 @@
+import { GraphQLError } from 'graphql';
 import { pipe, share, filter, merge, map, tap } from 'wonka';
 import { Exchange, OperationResult, Operation } from '../types';
 import { CombinedError } from '../utils';
@@ -5,8 +6,8 @@ import { CombinedError } from '../utils';
 export interface SerializedResult {
   data?: any;
   error?: {
+    graphQLErrors: Array<Partial<GraphQLError> | string>;
     networkError?: string;
-    graphQLErrors: string[];
   };
 }
 
@@ -35,9 +36,17 @@ const serializeResult = ({
   error,
 }: OperationResult): SerializedResult => {
   const result: SerializedResult = { data, error: undefined };
-  if (error !== undefined) {
+  if (error) {
     result.error = {
-      graphQLErrors: error.graphQLErrors.map(x => x.message),
+      graphQLErrors: error.graphQLErrors.map(error => {
+        if (!error.path && !error.extensions) return error.message;
+
+        return {
+          message: error.message,
+          path: error.path,
+          extensions: error.extensions,
+        };
+      }),
       networkError: error.networkError ? '' + error.networkError : undefined,
     };
   }
diff --git a/packages/core/src/utils/error.ts b/packages/core/src/utils/error.ts
index 53526a6e..f5e95c40 100644
--- a/packages/core/src/utils/error.ts
+++ b/packages/core/src/utils/error.ts
@@ -50,7 +50,7 @@ export class CombinedError extends Error {
     response,
   }: {
     networkError?: Error;
-    graphQLErrors?: Array<string | GraphQLError | Error>;
+    graphQLErrors?: Array<string | Partial<GraphQLError> | Error>;
     response?: any;
   }) {
     const normalizedGraphQLErrors = (graphQLErrors || []).map(
diff --git a/packages/next-urql/src/types.ts b/packages/next-urql/src/types.ts
index a8bdc90f..4dd966c4 100644
--- a/packages/next-urql/src/types.ts
+++ b/packages/next-urql/src/types.ts
@@ -1,3 +1,4 @@
+import { GraphQLError } from 'graphql';
 import { NextPageContext } from 'next';
 import { ClientOptions, Exchange, Client } from 'urql';
 import { AppContext } from 'next/app';
@@ -38,8 +39,8 @@ export interface WithUrqlProps extends WithUrqlClient, WithUrqlState {
 export interface SerializedResult {
   data?: any;
   error?: {
+    graphQLErrors: Array<Partial<GraphQLError> | string>;
     networkError?: string;
-    graphQLErrors: string[];
   };
 }
 


Test patch:
diff --git a/packages/core/src/exchanges/ssr.test.ts b/packages/core/src/exchanges/ssr.test.ts
index 7c4f81c1..70a88ee7 100644
--- a/packages/core/src/exchanges/ssr.test.ts
+++ b/packages/core/src/exchanges/ssr.test.ts
@@ -71,6 +71,35 @@ it('caches errored query results correctly', () => {
   });
 });
 
+it('caches complex GraphQLErrors in query results correctly', () => {
+  output.mockReturnValueOnce({
+    ...queryResponse,
+    data: null,
+    error: new CombinedError({
+      graphQLErrors: [
+        {
+          message: 'Oh no!',
+          path: ['Query'],
+          extensions: { test: true },
+        },
+      ],
+    }),
+  });
+
+  const ssr = ssrExchange();
+  const { source: ops$, next } = input;
+  const exchange = ssr(exchangeInput)(ops$);
+
+  publish(exchange);
+  next(queryOperation);
+
+  const error = ssr.extractData()[queryOperation.key].error;
+
+  expect(error).toHaveProperty('graphQLErrors.0.message', 'Oh no!');
+  expect(error).toHaveProperty('graphQLErrors.0.path', ['Query']);
+  expect(error).toHaveProperty('graphQLErrors.0.extensions.test', true);
+});
+
 it('resolves cached query results correctly', () => {
   const onPush = jest.fn();