Skip to content

Commit

Permalink
feat(errors): added handleErrors option (#723)
Browse files Browse the repository at this point in the history
  • Loading branch information
pyramation authored and benjie committed Mar 23, 2018
1 parent d0283d5 commit 051dcf7
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 5 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ npm-debug.log*
build/
TODO.md
package-lock.json
*.log
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,64 @@ for (const [name, createServerFromHandler] of Array.from(serverCreators)) {
})
})

test('will allow user to customize errors when handleErrors is set', async () => {
pgPool.connect.mockClear()
pgClient.query.mockClear()
pgClient.release.mockClear()
const server = createServer({
handleErrors: (errors) => {
return errors.map(error => {
error.message = 'my custom error message'
error.hint = 'my custom error hint'
error.detail = 'my custom error detail'
return error
})
},
})
await request(server)
.post('/graphql')
.send({ query: '{testError}' })
.expect(200)
.expect('Content-Type', /json/)
.expect({
data: { testError: null },
errors: [
{
message: 'my custom error message',
locations: [{ line: 1, column: 2 }],
path: ['testError'],
hint: 'my custom error hint',
detail: 'my custom error detail',
},
],
})
})

test('will allow user to send custom responses when handleErrors is set and sends a response', async () => {
pgPool.connect.mockClear()
pgClient.query.mockClear()
pgClient.release.mockClear()
const server = createServer({
handleErrors: (errors, req, res) => {
res.statusCode = 401
return errors
},
})
await request(server)
.post('/graphql')
.send({ query: '{testError}' })
.expect(401, {
errors: [
{
message: 'test message',
locations: [{ line: 1, column: 2 }],
path: ['testError'],
},
],
data: { testError: null },
})
})

test('will serve a favicon when graphiql is enabled', async () => {
const server1 = createServer({ graphiql: true })
const server2 = createServer({ graphiql: true, route: '/graphql' })
Expand Down
11 changes: 8 additions & 3 deletions src/postgraphile/http/createPostGraphileHttpRequestHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -540,11 +540,16 @@ export default function createPostGraphileHttpRequestHandler(options) {
} finally {
// Finally, we send the client the contents of `result`.
debugRequest('GraphQL query has been executed.')
// Format our errors so the client doesn鈥檛 get the full thing.
if (result && result.errors)
result.errors = result.errors.map(formatError)

res.setHeader('Content-Type', 'application/json; charset=utf-8')

// Format our errors so the client doesn鈥檛 get the full thing.
const handleErrors = options.handleErrors || (errors => errors.map(formatError))

if (result && result.errors) {
result.errors = handleErrors(result.errors, req, res)
}

res.end(JSON.stringify(result))

debugRequest('GraphQL query request finished.')
Expand Down
17 changes: 15 additions & 2 deletions src/postgraphile/postgraphile.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Pool, PoolConfig } from 'pg'
import { parse as parsePgConnectionString } from 'pg-connection-string'
import { GraphQLSchema } from 'graphql'
import { GraphQLSchema, GraphQLError } from 'graphql'
import { EventEmitter } from 'events'
import { createPostGraphileSchema, watchPostGraphileSchema } from 'postgraphile-core'
import createPostGraphileHttpRequestHandler, { HttpRequestHandler } from './http/createPostGraphileHttpRequestHandler'
import exportPostGraphileSchema from './schema/exportPostGraphileSchema'
import { IncomingMessage, ServerResponse } from 'http'
import jwt = require('jsonwebtoken')
import { GraphQLErrorExtended } from './extendedFormatError'

// Please note that the comments for this type are turned into documentation
// automatically. We try and specify the options in the same order as the CLI.
Expand Down Expand Up @@ -45,6 +46,14 @@ type PostGraphileOptions = {
// `json` (which causes the stack to become an array with elements for each
// line of the stack).
showErrorStack?: boolean,
// Enables ability to modify errors before sending them down to the client
// optionally can send down custom responses
/* @middlewareOnly */
handleErrors?: ((
errors: Array<GraphQLError>,
req: IncomingMessage,
res: ServerResponse,
) => Array<GraphQLErrorExtended>);
// Extends the error response with additional details from the Postgres
// error. Can be any combination of `['hint', 'detail', 'errcode']`.
// Default is `[]`.
Expand Down Expand Up @@ -134,7 +143,7 @@ type PostGraphileOptions = {
// function which will return the same (or a Promise to the same) based on
// the incoming web request (e.g. to extract session data)
/* @middlewareOnly */
pgSettings?: { [key: string]: mixed } | ((req: IncomingMessage) => Promise<{[key: string]: mixed }>),
pgSettings?: { [key: string]: mixed } | ((req: IncomingMessage) => Promise<{ [key: string]: mixed }>),
// Some graphile-build plugins may need additional information available on
// the `context` argument to the resolver - you can use this function to
// provide such information based on the incoming request - you can even use
Expand All @@ -161,6 +170,10 @@ export function getPostgraphileSchemaBuilder(pgPool: Pool, schema: string | Arra
console.warn('WARNING: jwtSecret provided, however jwtPgTypeIdentifier (token identifier) not provided.')
}

if (options.handleErrors && (options.extendedErrors || options.showErrorStack)) {
throw new Error(`You cannot combine 'handleErrors' with the other error options`)
}

// Creates the Postgres schemas array.
const pgSchemas: Array<string> = Array.isArray(schema) ? schema : [schema]

Expand Down

0 comments on commit 051dcf7

Please sign in to comment.