Repository: mercurius-js/mercurius
Pull request #679 (2021-12-02 17:31:25)

Extend regression fix in handling bad json to include custom context
This PR extend #678 to include custom context in error formatter.

@mcollina I am not sure if context (custom and/or default) is needed in case when custom error handler has been used (as in is such case error formatter is not called at all with my understanding?)

mcollina: > @mcollina I am not sure if context (custom and/or default) is needed in case when custom error handler has been used (as in is such case error formatter is not called at all with my understanding?)

I think it's needed. Folks would expect it to be there.

Patch:
diff --git a/lib/routes.js b/lib/routes.js
index f305797..8846130 100644
--- a/lib/routes.js
+++ b/lib/routes.js
@@ -133,22 +133,33 @@ function tryJSONParse (request, value) {
 
 module.exports = async function (app, opts) {
   const errorFormatter = typeof opts.errorFormatter === 'function' ? opts.errorFormatter : defaultErrorFormatter
+  const contextFn = opts.context
 
   if (typeof opts.errorHandler === 'function') {
-    app.setErrorHandler((error, request, reply) => {
+    app.setErrorHandler(async (error, request, reply) => {
       const errorHandler = opts.errorHandler
       if (!request[kRequestContext]) {
         // Generate the context for this request
-        request[kRequestContext] = { reply, app }
+        if (contextFn) {
+          request[kRequestContext] = await contextFn(request, reply)
+          Object.assign(request[kRequestContext], { reply, app })
+        } else {
+          request[kRequestContext] = { reply, app }
+        }
       }
 
       return errorHandler(error, request, reply)
     })
   } else if (opts.errorHandler === true || opts.errorHandler === undefined) {
-    app.setErrorHandler((error, request, reply) => {
+    app.setErrorHandler(async (error, request, reply) => {
       if (!request[kRequestContext]) {
         // Generate the context for this request
-        request[kRequestContext] = { reply, app }
+        if (contextFn) {
+          request[kRequestContext] = await contextFn(request, reply)
+          Object.assign(request[kRequestContext], { reply, app })
+        } else {
+          request[kRequestContext] = { reply, app }
+        }
       }
 
       const { statusCode, response } = errorFormatter(
@@ -158,7 +169,6 @@ module.exports = async function (app, opts) {
       return reply.code(statusCode).send(response)
     })
   }
-  const contextFn = opts.context
   const { subscriptionContextFn } = opts
 
   app.decorateRequest(kRequestContext)


Test patch:
diff --git a/test/errors.js b/test/errors.js
index 1c1ae1b..005cde6 100644
--- a/test/errors.js
+++ b/test/errors.js
@@ -5,6 +5,7 @@ const Fastify = require('fastify')
 const GQL = require('..')
 const { ErrorWithProps } = GQL
 const { FederatedError } = require('../lib/errors')
+const { kRequestContext } = require('../lib/symbols')
 const split = require('split2')
 
 test('ErrorWithProps - support status code in the constructor', async (t) => {
@@ -808,6 +809,55 @@ test('bad json', async (t) => {
   )
 })
 
+test('bad json with custom error formatter and custom context', async (t) => {
+  const schema = `
+    type Query {
+      successful: String
+    }
+  `
+
+  const resolvers = {
+    Query: {
+      successful () {
+        t.fail('Should not be called')
+        return 'Runs OK'
+      }
+    }
+  }
+
+  const app = Fastify()
+
+  app.register(GQL, {
+    schema,
+    resolvers,
+    context: (_request, _reply) => ({ customValue: true }),
+    errorFormatter: (_execution, context) => {
+      t.equal(context.customValue, true)
+      t.pass('custom error formatter called')
+      return {
+        statusCode: 400,
+        response: { data: null, errors: [{ message: 'Unexpected token h' }] }
+      }
+    }
+  })
+
+  await app.ready()
+
+  const res = await app.inject({
+    method: 'POST',
+    headers: {
+      'content-type': 'application/json'
+    },
+    body: 'this is not a json',
+    url: '/graphql'
+  })
+
+  t.equal(res.statusCode, 400)
+  t.same(res.json(),
+    { data: null, errors: [{ message: 'Unexpected token h' }] }
+  )
+})
+
 test('bad json with custom error handler', async (t) => {
   t.plan(3)
   const schema = `
@@ -854,3 +904,52 @@ test('bad json with custom error handler', async (t) => {
     is: 'error'
   })
 })
+
+test('bad json with custom error handler, custom error formatter and custom context', async (t) => {
+  const schema = `
+    type Query {
+      successful: String
+    }
+  `
+
+  const resolvers = {
+    Query: {
+      successful () {
+        t.fail('Should not be called')
+        return 'Runs OK'
+      }
+    }
+  }
+
+  const app = Fastify()
+
+  app.register(GQL, {
+    schema,
+    resolvers,
+    context: (_request, _reply) => ({ customValue: true }),
+
+    errorHandler: (_, request, reply) => {
+      t.equal(request[kRequestContext].customValue, true)
+      t.pass('custom error handler called')
+      reply.code(400).send({
+        is: 'error'
+      })
+    }
+  })
+
+  await app.ready()
+
+  const res = await app.inject({
+    method: 'POST',
+    headers: {
+      'content-type': 'application/json'
+    },
+    body: 'this is not a json',
+    url: '/graphql'
+  })
+
+  t.equal(res.statusCode, 400)
+  t.same(res.json(), {
+    is: 'error'
+  })
+})