RFC: Error handling
mxstbr opened this issue · comments
Summary
Error handling is a hotly debated topic in the GraphQL ecosystem that comes with no broadly recommended best practice today.
Proposed Solution
The way we handle this at Stellate (which we quite like) looks like this:
import { GraphQLError } from 'graphql'
export abstract class StellateError extends GraphQLError {
abstract readonly name: string
constructor(
message: string,
extensions: {
code: string
http?: {
status: number
}
},
) {
super(message, {
extensions,
})
}
}
/** For use when user is not authenticated or unknown. */
export class AuthenticationError extends StellateError {
name = 'UnauthenticatedError'
constructor(message = 'Unauthenticated') {
super(message, { http: { status: 401 }, code: 'UNAUTHENTICATED' })
}
}
/** For use when a resource is not found or not accessible by an authenticated user. */
export class ForbiddenError extends StellateError {
name = 'ForbiddenError'
constructor(message = 'Forbidden') {
super(message, { http: { status: 403 }, code: 'FORBIDDEN' })
}
}
/** For use when a resource is not found. */
export class NotFoundError extends StellateError {
name = 'NotFoundError'
constructor(message = 'Not Found') {
super(message, { http: { status: 404 }, code: 'NOT_FOUND' })
}
}
/** For use when any input was invalid or when a resource does not exist but is assumed to exist. */
export class BadRequestError extends StellateError {
name = 'BadRequestError'
constructor(message = 'Bad Request') {
super(message, { http: { status: 400 }, code: 'BAD_REQUEST' })
}
}
/** For use when any internal billing calls encounter invalid states. */
export class PaymentError extends StellateError {
name = 'PaymentError'
// TODO: Replace with more specific error per-service
constructor(message = 'An invalid Payment Request has been made') {
super(message, { http: { status: 400 }, code: 'BAD_PAYMENT_REQUEST' })
}
}
/** For use when any internal call or fetch request to another API fails. */
export class SlackCallError extends StellateError {
name = 'SlackAPIError'
// TODO: Replace with more specific error per-service
constructor(message = 'Internal call to Slack failed') {
super(message, { http: { status: 500 }, code: 'SLACK_API_ERROR' })
}
}
export class ValidationError extends StellateError {
name = 'ValidationError'
constructor(message: string) {
super(message, { code: 'GRAPHQL_VALIDATION_FAILED' })
}
}
export class UserInputError extends StellateError {
name = 'UserInputError'
constructor(message: string) {
super(message, { code: 'BAD_USER_INPUT' })
}
}
Todo: Figure out what it would look like to abstract this & guide people down the path of success of error handling in GraphQL with Fuse.js.
I'm too slow! @JoviDeCroock already added a first version here: e85e777