Repository: mercurius-js/mercurius (js)

Issue:
Add a statusCode optional parameter to ErrorWithProps
Instead of

```js
const err = new mercurius.ErrorWithProps('UNAUTHORIZED');
err.statusCode = 200
throw err
```

We should be doing

```js
throw new mercurius.ErrorWithProps('UNAUTHORIZED', 200);
```

Note that we should also add some documents on how errorFormatter works as there has been a few issues about it. There might be something more to change under the hood - we might want to return a `200` by default in case of errors for example (sigh).

_Originally posted by @mcollina in https://github.com/mercurius-js/auth/issues/32#issuecomment-932356247_

Pull request description:
feat: add status code support to ErrorWithProps
closes #592 

Patch:
diff --git a/docs/api/options.md b/docs/api/options.md
index 0bb2022..b6a76d9 100644
--- a/docs/api/options.md
+++ b/docs/api/options.md
@@ -1,20 +1,26 @@
 # mercurius
 
-- [Plugin options](#plugin-options)
-- [HTTP endpoints](#http-endpoints)
-  - [GET /graphql](#get-graphql)
-  - [POST /graphql](#post-graphql)
-  - [POST /graphql with Content-type: application/graphql](#post-graphql-with-content-type-applicationgraphql)
-  - [GET /graphiql](#get-graphiql)
-- [Decorators](#decorators)
-  - [app.graphql(source, context, variables, operationName)](#appgraphqlsource-context-variables-operationname)
-  - [app.graphql.extendSchema(schema), app.graphql.defineResolvers(resolvers) and app.graphql.defineLoaders(loaders)](#appgraphqlextendschemaschema-appgraphqldefineresolversresolvers-and-appgraphqldefineloadersloaders)
-  - [app.graphql.replaceSchema(schema)](#appgraphqlreplaceschemaschema)
-  - [app.graphql.transformSchema(transforms)](#appgraphqltransformschematransforms)
-  - [app.graphql.schema](#appgraphqlschema)
-  - [reply.graphql(source, context, variables, operationName)](#replygraphqlsource-context-variables-operationname)
-- [Error extensions](#use-errors-extension-to-provide-additional-information-to-query-errors)
-
+- [mercurius](#mercurius)
+  - [API](#api)
+    - [Plugin options](#plugin-options)
+      - [queryDepth example](#querydepth-example)
+    - [HTTP endpoints](#http-endpoints)
+      - [GET /graphql](#get-graphql)
+      - [POST /graphql](#post-graphql)
+      - [POST /graphql with Content-type: application/graphql](#post-graphql-with-content-type-applicationgraphql)
+      - [GET /graphiql](#get-graphiql)
+    - [Decorators](#decorators)
+      - [app.graphql(source, context, variables, operationName)](#appgraphqlsource-context-variables-operationname)
+      - [app.graphql.extendSchema(schema), app.graphql.defineResolvers(resolvers) and app.graphql.defineLoaders(loaders)](#appgraphqlextendschemaschema-appgraphqldefineresolversresolvers-and-appgraphqldefineloadersloaders)
+      - [app.graphql.replaceSchema(schema)](#appgraphqlreplaceschemaschema)
+      - [app.graphql.transformSchema(transforms)](#appgraphqltransformschematransforms)
+      - [app.graphql.schema](#appgraphqlschema)
+      - [reply.graphql(source, context, variables, operationName)](#replygraphqlsource-context-variables-operationname)
+    - [Errors](#errors)
+    - [ErrorWithProps](#errorwithprops)
+      - [Extensions](#extensions)
+      - [Status code](#status-code)
+    - [Error formatter](#error-formatter)
 ## API
 
 ### Plugin options
@@ -439,7 +445,33 @@ async function run() {
 run()
 ```
 
-### Use errors extension to provide additional information to query errors
+### Errors
+Mercurius help the error handling with two useful tools.
+
+- ErrorWithProps class
+- ErrorFormatter option
+
+### ErrorWithProps
+
+ErrorWithProps can be used to create Errors to be thrown inside the resolvers or plugins.
+
+it takes 3 parameters:
+
+- message
+- extensions
+- statusCode
+
+```js
+'use strict'
+
+throw new ErrorWithProps('message', {
+    ...
+}, 200)
+```
+
+#### Extensions
+
+Use errors `extensions` to provide additional information to query errors
 
 GraphQL services may provide an additional entry to errors with the key `extensions` in the result.
 
@@ -495,3 +527,31 @@ app.register(mercurius, {
 
 app.listen(3000)
 ```
+
+#### Status code
+
+To control the status code for the response, the third optional parameter can be used.
+
+```js
+
+    throw new mercurius.ErrorWithProps('Invalid User ID', {moreErrorInfo})
+    // using de defaultErrorFormatter the response statusCode will be 500
+
+    throw new mercurius.ErrorWithProps('Invalid User ID', {moreErrorInfo}, 200)
+    // using de defaultErrorFormatter the response statusCode will be 200
+
+    const error = new mercurius.ErrorWithProps('Invalid User ID', {moreErrorInfo}, 500)
+    error.data = {foo: 'bar'} 
+    throw error
+    // using de defaultErrorFormatter the response status code will be always 200 because error.data is defined
+
+
+```
+
+### Error formatter
+
+Allows the status code of the response to be set, and a GraphQL response for the error to be defined. 
+
+By default uses the defaultErrorFormatter, but it can be overridden in the [mercurius options](/docs/api/options.md#plugin-options) changing the errorFormatter parameter.
+
+**Important**: *using the default formatter, when the error has a data property the response status code will be always 200*
diff --git a/index.d.ts b/index.d.ts
index 5ba18d8..aeb54dc 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -592,11 +592,12 @@ declare namespace mercurius {
    * Extended errors for adding additional information in error responses
    */
   class ErrorWithProps extends Error {
-    constructor(message: string, extensions?: object);
+    constructor(message: string, extensions?: object, statusCode?: number);
     /**
      * Custom additional properties of this error
      */
     extensions?: object;
+    statusCode?: number;
   }
 
   /**
diff --git a/lib/errors.js b/lib/errors.js
index ebeef58..09abe47 100644
--- a/lib/errors.js
+++ b/lib/errors.js
@@ -4,9 +4,10 @@ const { formatError, GraphQLError } = require('graphql')
 const createError = require('fastify-error')
 
 class ErrorWithProps extends Error {
-  constructor (message, extensions) {
+  constructor (message, extensions, statusCode) {
     super(message)
     this.extensions = extensions
+    this.statusCode = statusCode || 500
   }
 }
 


Test patch:
diff --git a/test/errors.js b/test/errors.js
index 722372f..d01010a 100644
--- a/test/errors.js
+++ b/test/errors.js
@@ -7,6 +7,11 @@ const { ErrorWithProps } = GQL
 const { FederatedError } = require('../lib/errors')
 const split = require('split2')
 
+test('ErrorWithProps - support status code in the constructor', async (t) => {
+  const error = new ErrorWithProps('error', { }, 500)
+  t.equal(error.statusCode, 500)
+})
+
 test('errors - multiple extended errors', async (t) => {
   const schema = `
     type Query {
@@ -726,3 +731,39 @@ test('app.graphql which throws, with JIT enabled, twice', async (t) => {
 
   t.equal(errors.length, 0)
 })
+
+test('errors - should override statusCode to 200 if the data is present', async (t) => {
+  const schema = `
+    type Query {
+      error: String
+      successful: String
+    }
+  `
+
+  const resolvers = {
+    Query: {
+      error () {
+        throw new ErrorWithProps('Error', undefined, 500)
+      },
+      successful () {
+        return 'Runs OK'
+      }
+    }
+  }
+
+  const app = Fastify()
+
+  app.register(GQL, {
+    schema,
+    resolvers
+  })
+
+  await app.ready()
+
+  const res = await app.inject({
+    method: 'GET',
+    url: '/graphql?query={error,successful}'
+  })
+
+  t.equal(res.statusCode, 200)
+})
diff --git a/test/types/index.ts b/test/types/index.ts
index a7c39de..64ebfa6 100644
--- a/test/types/index.ts
+++ b/test/types/index.ts
@@ -1,4 +1,4 @@
-import { expectAssignable } from 'tsd'
+import { expectAssignable, expectError } from 'tsd'
 /* eslint-disable no-unused-expressions */
 import { EventEmitter } from 'events'
 // eslint-disable-next-line no-unused-vars
@@ -593,3 +593,9 @@ app.graphql.addHook('onGatewayReplaceSchema', async function (instance, schema)
   expectAssignable<FastifyInstance>(instance)
   expectAssignable<GraphQLSchema>(schema)
 })
+
+expectError(() => {
+  return new mercurius.ErrorWithProps('mess', {}, 'wrong statusCode')
+})
+
+expectAssignable<Error>(new mercurius.ErrorWithProps('mess', {}, 200))