Repository: mcollina/fastify-gql (js)

Issue:
Error management issue on `/graphql` endpoint
if I run this script:
```javascript
'use strict'

const Fastify = require('fastify')
const GQL = require('fastify-gql')
const makeExecutableSchema = require('graphql-tools').makeExecutableSchema

const app = Fastify()

const schema = [
  `
  type Query {
    history(id: ID!): [History]
  }
  `,
  `
  type History {
      id: ID!
      name: String!
      created: Date!
      sysPeriod: Range!
    }
  `,
  `
  type Range {
    begin: Date!,
    end: Date!
  }
  `,
  `
    scalar Date
  `
]

const resolvers = {
  Query: {
    history: async ({ id }) => {
      return Promise.resolve({ id, name: 'test', created: new Date(), sysPeriod: 'not a range!' })
    }
  }
}

app.register(GQL, {
  schema: makeExecutableSchema({ typeDefs: schema, resolvers: [resolvers] }),
  graphiql: true
})

app.get('/', async function (req, reply) {
  console.log('/')
  const query = `
  {
    history(id: "1") {
      id
      name
      created
      sysPeriod {
        begin
        end
      }
    }
  }
  `
  return reply.graphql(query)
})

app.listen(7000)
```

then do a curl to hit `/` like this, I have http 200 +  some error reporting:
```console
curl http://localhost:7000 | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   170  100   170    0     0   3269      0 --:--:-- --:--:-- --:--:--  3269
{
  "errors": [
    {
      "message": "Expected Iterable, but did not find one for field Query.history.",
      "locations": [
        {
          "line": 3,
          "column": 5
        }
      ],
      "path": [
        "history"
      ]
    }
  ],
  "data": {
    "history": null
  }
}
```

but if I go to `/graphiql` with a browser and run the same query:
```graphql
{
  history(id: "1") {
    id
    name
    created
    sysPeriod {
      begin
      end
    }
  }
}
```
I still have a http 200, `null` `data.history`, but no errors reported.

I believe I should not have a `http 200` as a result, probably an `http 500`, and it should probably detail the errors

Pull request description:
Add support for execution errors
fixes #7 

Patch:
diff --git a/index.js b/index.js
index feda580..e61cc5e 100644
--- a/index.js
+++ b/index.js
@@ -3,7 +3,7 @@
 const fp = require('fastify-plugin')
 const LRU = require('tiny-lru')
 const routes = require('./routes')
-const { BadRequest, MethodNotAllowed } = require('http-errors')
+const { BadRequest, MethodNotAllowed, InternalServerError } = require('http-errors')
 const {
   parse,
   buildSchema,
@@ -170,9 +170,7 @@ module.exports = fp(async function (app, opts) {
       }
     }
 
-    // Execute
-    // This may throw. Where is the error going?
-    return execute(
+    const execution = await execute(
       schema,
       document,
       root,
@@ -180,5 +178,12 @@ module.exports = fp(async function (app, opts) {
       variables,
       operationName
     )
+    if (execution.errors) {
+      const err = new InternalServerError()
+      err.errors = execution.errors
+      throw err
+    }
+
+    return execution
   }
 })


Test patch:
diff --git a/tap-snapshots/test-routes.js-TAP.test.js b/tap-snapshots/test-routes.js-TAP.test.js
index 2fc2ac1..9b8345e 100644
--- a/tap-snapshots/test-routes.js-TAP.test.js
+++ b/tap-snapshots/test-routes.js-TAP.test.js
@@ -5,6 +5,44 @@
  * Make sure to inspect the output below.  Do not ignore changes!
  */
 'use strict'
+exports[`test/routes.js TAP GET return 500 on resolver error > undefined 1`] = `
+{
+  "errors": [
+    {
+      "message": "this is a dummy error",
+      "locations": [
+        {
+          "line": 1,
+          "column": 2
+        }
+      ],
+      "path": [
+        "add"
+      ]
+    }
+  ]
+}
+`
+
+exports[`test/routes.js TAP POST return 500 on resolver error > undefined 1`] = `
+{
+  "errors": [
+    {
+      "message": "this is a dummy error",
+      "locations": [
+        {
+          "line": 1,
+          "column": 2
+        }
+      ],
+      "path": [
+        "add"
+      ]
+    }
+  ]
+}
+`
+
 exports[`test/routes.js TAP POST return 400 on error > undefined 1`] = `
 {
   "errors": [
diff --git a/test/routes.js b/test/routes.js
index 977d128..af5493d 100644
--- a/test/routes.js
+++ b/test/routes.js
@@ -217,6 +217,62 @@ test('disable routes', async (t) => {
   t.deepEqual(res.statusCode, 404)
 })
 
+test('GET return 500 on resolver error', async (t) => {
+  const app = Fastify()
+  const schema = `
+    type Query {
+      add(x: Int, y: Int): Int
+    }
+  `
+
+  const resolvers = {
+    add: async ({ x, y }) => { throw new Error('this is a dummy error') }
+  }
+
+  app.register(GQL, {
+    schema,
+    resolvers
+  })
+
+  const res = await app.inject({
+    method: 'GET',
+    url: '/graphql?query={add(x:2,y:2)}'
+  })
+
+  t.equal(res.statusCode, 500) // Internal Server Error
+  t.matchSnapshot(JSON.stringify(JSON.parse(res.body), null, 2))
+})
+
+test('POST return 500 on resolver error', async (t) => {
+  const app = Fastify()
+  const schema = `
+    type Query {
+      add(x: Int, y: Int): Int
+    }
+  `
+
+  const resolvers = {
+    add: async ({ x, y }) => { throw new Error('this is a dummy error') }
+  }
+
+  app.register(GQL, {
+    schema,
+    resolvers
+  })
+
+  const query = '{add(x:2,y:2)}'
+  const res = await app.inject({
+    method: 'POST',
+    url: '/graphql',
+    body: {
+      query
+    }
+  })
+
+  t.equal(res.statusCode, 500) // Internal Server Error
+  t.matchSnapshot(JSON.stringify(JSON.parse(res.body), null, 2))
+})
+
 test('POST return 400 on error', async (t) => {
   const app = Fastify()
   const schema = `