Repository: mcollina/fastify-gql (js)

Issue:
Error extensions not available when query is jit'ed
While using `ErrorWithProps`, the extensions is not available when query is not jit'ed.

This happens because `graphql-jit` [expects extensions property inside the originalError](https://github.com/zalando-incubator/graphql-jit/blob/d2048c8c37e43f04a1589f6eba5fa0c8935f7d09/src/error.ts#L18).

A simple fix would be to not have explicit `code` field in `ErrorWithProps` and rename `additionalProperties` field to `extensions` and expect users to pass the same inside the `extensions` field itself.

Pull request:
Fixes missing error extensions when query is jit'ed
Fixes #164 

Removes explicit `code` field in `ErrorWithProps` and renames `additionalProperties` field to `extensions`.

Patch:
diff --git a/README.md b/README.md
index d5c52a8..a45b5f6 100644
--- a/README.md
+++ b/README.md
@@ -555,7 +555,7 @@ const resolvers = {
     findUser: (_, { id }) => {
       const user = users[id]
       if (user) return users[id]
-      else throw new ErrorWithProps('Invalid User ID', "USER_ID_INVALID", { id, timestamp: Math.round(new Date().getTime()/1000) })
+      else throw new ErrorWithProps('Invalid User ID', { id, code: "USER_ID_INVALID", timestamp: Math.round(new Date().getTime()/1000) })
     }
   }
 }
diff --git a/index.d.ts b/index.d.ts
index bee519c..7b79b66 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -140,15 +140,11 @@ declare namespace fastifyGQL {
    * Extended errors for adding additional information in error responses
    */
   export class ErrorWithProps extends Error {
-    constructor (message: string, code?: string, additionalProperties?: object)
-    /**
-     * Custom error code of this error
-     */
-    code?: string
+    constructor (message: string, extensions?: object)
     /**
      * Custom additional properties of this error
      */
-    additionalProperties?: object
+    extensions?: object
   } 
 }
 
diff --git a/index.js b/index.js
index 050e067..bb3c8dc 100644
--- a/index.js
+++ b/index.js
@@ -384,11 +384,8 @@ const plugin = fp(async function (app, opts) {
 
     if (execution.errors) {
       execution.errors = execution.errors.map(e => {
-        if (e.originalError && (Object.prototype.hasOwnProperty.call(e.originalError, 'code') || Object.prototype.hasOwnProperty.call(e.originalError, 'additionalProperties'))) {
-          return new GraphQLError(e.originalError.message, e.nodes, e.source, e.positions, e.path, e.originalError, {
-            code: e.originalError.code,
-            ...e.originalError.additionalProperties
-          })
+        if (e.originalError && (Object.prototype.hasOwnProperty.call(e.originalError, 'extensions'))) {
+          return new GraphQLError(e.originalError.message, e.nodes, e.source, e.positions, e.path, e.originalError, e.originalError.extensions)
         }
         return e
       })
diff --git a/lib/errors.js b/lib/errors.js
index 0684d14..86adc1b 100644
--- a/lib/errors.js
+++ b/lib/errors.js
@@ -1,10 +1,9 @@
 'use strict'
 
 class ErrorWithProps extends Error {
-  constructor (message, code, additionalProperties) {
+  constructor (message, extensions) {
     super(message)
-    this.code = code
-    this.additionalProperties = additionalProperties
+    this.extensions = extensions
   }
 }
 


Test patch:
diff --git a/test/errors.js b/test/errors.js
index 9358e40..ee8c60d 100644
--- a/test/errors.js
+++ b/test/errors.js
@@ -8,19 +8,15 @@ const { ErrorWithProps } = GQL
 test('errors - multiple extended errors', async (t) => {
   const schema = `
     type Query {
-      errorOne: String
-      errorTwo: String
+      error: String
       successful: String
     }
   `
 
   const resolvers = {
     Query: {
-      errorOne () {
-        throw new ErrorWithProps('Error One', 'ERROR_ONE', { additional: 'information one', other: 'data one' })
-      },
-      errorTwo () {
-        throw new ErrorWithProps('Error Two', 'ERROR_TWO', { additional: 'information two', other: 'data two' })
+      error () {
+        throw new ErrorWithProps('Error', { code: 'ERROR', additional: 'information', other: 'data' })
       },
       successful () {
         return 'Runs OK'
@@ -39,52 +35,36 @@ test('errors - multiple extended errors', async (t) => {
 
   const res = await app.inject({
     method: 'GET',
-    url: '/graphql?query={errorOne,errorTwo, successful}'
+    url: '/graphql?query={error,successful}'
   })
 
   t.equal(res.statusCode, 200)
   t.deepEqual(JSON.parse(res.payload), {
     data: {
-      errorOne: null,
-      errorTwo: null,
+      error: null,
       successful: 'Runs OK'
     },
     errors: [
       {
-        message: 'Error One',
+        message: 'Error',
         locations: [
           {
             line: 1,
             column: 2
           }
         ],
-        path: ['errorOne'],
+        path: ['error'],
         extensions: {
-          code: 'ERROR_ONE',
-          additional: 'information one',
-          other: 'data one'
-        }
-      },
-      {
-        message: 'Error Two',
-        locations: [
-          {
-            line: 1,
-            column: 11
-          }
-        ],
-        path: ['errorTwo'],
-        extensions: {
-          code: 'ERROR_TWO',
-          additional: 'information two',
-          other: 'data two'
+          code: 'ERROR',
+          additional: 'information',
+          other: 'data'
         }
       }
     ]
   })
 })
 
-test('errors - extended errors with number additionalProperties', async (t) => {
+test('errors - extended errors with number extensions', async (t) => {
   const schema = `
     type Query {
       willThrow: String
@@ -94,7 +74,7 @@ test('errors - extended errors with number additionalProperties', async (t) => {
   const resolvers = {
     Query: {
       willThrow () {
-        throw new ErrorWithProps('Extended Error', 'EXTENDED_ERROR', { floating: 3.14, timestamp: 1324356, reason: 'some reason' })
+        throw new ErrorWithProps('Extended Error', { code: 'EXTENDED_ERROR', floating: 3.14, timestamp: 1324356, reason: 'some reason' })
       }
     }
   }
@@ -155,13 +135,7 @@ test('errors - extended errors optional parameters', async (t) => {
         throw new ErrorWithProps('Extended Error')
       },
       two () {
-        throw new ErrorWithProps('Extended Error', 'ERROR_TWO')
-      },
-      three () {
-        throw new ErrorWithProps('Extended Error', 'ERROR_THREE', { reason: 'some reason' })
-      },
-      four () {
-        throw new ErrorWithProps('Extended Error', undefined, { reason: 'some reason' })
+        throw new ErrorWithProps('Extended Error', { code: 'ERROR_TWO', reason: 'some reason' })
       }
     }
   }
@@ -177,16 +151,14 @@ test('errors - extended errors optional parameters', async (t) => {
 
   const res = await app.inject({
     method: 'GET',
-    url: '/graphql?query={one,two,three,four}'
+    url: '/graphql?query={one,two}'
   })
 
   t.equal(res.statusCode, 200)
   t.deepEqual(JSON.parse(res.payload), {
     data: {
       one: null,
-      two: null,
-      three: null,
-      four: null
+      two: null
     },
     errors: [
       {
@@ -197,8 +169,7 @@ test('errors - extended errors optional parameters', async (t) => {
             column: 2
           }
         ],
-        path: ['one'],
-        extensions: {}
+        path: ['one']
       },
       {
         message: 'Extended Error',
@@ -210,34 +181,73 @@ test('errors - extended errors optional parameters', async (t) => {
         ],
         path: ['two'],
         extensions: {
-          code: 'ERROR_TWO'
-        }
-      },
-      {
-        message: 'Extended Error',
-        locations: [
-          {
-            line: 1,
-            column: 10
-          }
-        ],
-        path: ['three'],
-        extensions: {
-          code: 'ERROR_THREE',
+          code: 'ERROR_TWO',
           reason: 'some reason'
         }
+      }
+    ]
+  })
+})
+
+test('errors - errors with jit enabled', async (t) => {
+  const schema = `
+    type Query {
+      error: String
+      successful: String
+    }
+  `
+
+  const resolvers = {
+    Query: {
+      error () {
+        throw new ErrorWithProps('Error', { code: 'ERROR', additional: 'information', other: 'data' })
       },
+      successful () {
+        return 'Runs OK'
+      }
+    }
+  }
+
+  const app = Fastify()
+
+  app.register(GQL, {
+    schema,
+    resolvers,
+    jit: 1
+  })
+
+  await app.ready()
+
+  await app.inject({
+    method: 'GET',
+    url: '/graphql?query={error,successful}'
+  })
+
+  const res = await app.inject({
+    method: 'GET',
+    url: '/graphql?query={error,successful}'
+  })
+
+  t.equal(res.statusCode, 200)
+  t.deepEqual(JSON.parse(res.payload), {
+    data: {
+      error: null,
+      successful: 'Runs OK'
+    },
+    errors: [
       {
-        message: 'Extended Error',
+        message: 'Error',
         locations: [
           {
             line: 1,
-            column: 16
+            column: 2
           }
         ],
-        path: ['four'],
+        path: ['error'],
         extensions: {
-          reason: 'some reason'
+          code: 'ERROR',
+          additional: 'information',
+          other: 'data'
         }
       }
     ]
diff --git a/test/types/index.ts b/test/types/index.ts
index 28e779c..60953e9 100644
--- a/test/types/index.ts
+++ b/test/types/index.ts
@@ -89,7 +89,7 @@ app.register(async function (app) {
   `)
   app.graphql.defineResolvers({
     Query: {
-      willThrow: async () => { throw new ErrorWithProps('Extended Error', 'EXTENDED_ERROR', { reason: 'some reason', other: 32 }) }
+      willThrow: async () => { throw new ErrorWithProps('Extended Error', { code: 'EXTENDED_ERROR', reason: 'some reason', other: 32 }) }
     }
   })
 })