diff --git a/index.js b/index.js index 4d8da8e..2584a8f 100644 --- a/index.js +++ b/index.js @@ -90,11 +90,39 @@ function createError (code, message, statusCode = 500, Base = Error, captureStac return FastifyError } +function createRFC7807Error ( + code, + message, + statusCode = 500, + opts = {}, + Base = Error, + captureStackTrace = createError.captureStackTrace +) { + const Err = createError(code, message, statusCode, Base, captureStackTrace) + + Err.type = opts.type || 'about:blank' + Err.title = opts.title || 'FastifyError' + + Err.prototype.toRFC7807 = function (instance = '', details = {}) { + return { + type: Err.type, + title: Err.title, + status: this.statusCode ?? statusCode, + detail: this.message, + instance, + code: this.code, + details + } + } + return Err +} + createError.captureStackTrace = true const FastifyErrorConstructor = createError(FastifyGenericErrorSymbol, 'Fastify Error', 500, Error) module.exports = createError module.exports.FastifyError = FastifyErrorConstructor +module.exports.createRFC7807Error = createRFC7807Error module.exports.default = createError module.exports.createError = createError diff --git a/test/index.test.js b/test/index.test.js index 3ad97f6..3dc09b1 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,7 +1,7 @@ 'use strict' const test = require('node:test') -const { createError, FastifyError } = require('..') +const { createError, FastifyError, createRFC7807Error } = require('..') test('Create error with zero parameter', (t) => { t.plan(6) @@ -230,3 +230,45 @@ test('check if FastifyError is instantiable', (t) => { t.assert.ok(err instanceof FastifyError) t.assert.ok(err instanceof Error) }) + +test('toRFC7807() with defaults', (t) => { + t.plan(1) + + const E = createRFC7807Error('CODE', 'oops') + const e = new E() + const pd = e.toRFC7807('/test') + + t.assert.deepStrictEqual(pd, { + type: 'about:blank', + title: 'FastifyError', + status: 500, + detail: 'oops', + instance: '/test', + code: 'CODE', + details: {} + }) +}) + +test('toRFC7807() merges extension details', (t) => { + t.plan(1) + + const Warning = createRFC7807Error( + 'WARN', + 'Threshold exceeded by %d', + 429, + { type: 'https://example.net/probs/warn', title: 'Warning' } + ) + const w = new Warning(7) + + const pd = w.toRFC7807('/limit', { retryAfter: 60, exceeded: true }) + + t.assert.deepStrictEqual(pd, { + type: 'https://example.net/probs/warn', + title: 'Warning', + status: 429, + detail: 'Threshold exceeded by 7', + instance: '/limit', + code: 'WARN', + details: { retryAfter: 60, exceeded: true } + }) +}) diff --git a/types/index.d.ts b/types/index.d.ts index 2e59d9f..14fd063 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -28,7 +28,9 @@ declare namespace createError { export interface FastifyError extends Error { code: string name: string - statusCode?: number + statusCode?: number, + toRFC7807?(instance?: string, details?: Record): RFC7807Problem + } export interface FastifyErrorConstructor< @@ -40,6 +42,29 @@ declare namespace createError { readonly prototype: FastifyError & E } + export function createRFC7807Error< + C extends string, + SC extends number, + Arg extends unknown[] = [any?, any?, any?] +> ( + code: C, + message: string, + statusCode?: SC, + opts?: { type?: string; title?: string }, + Base?: ErrorConstructor, + captureStackTrace?: boolean + ): createError.FastifyErrorConstructor<{ code: C; statusCode: SC }, Arg> + + export interface RFC7807Problem { + type: string + title: string + status: number + detail: string + instance?: string + code: string + details?: Record + } + export const FastifyError: FastifyErrorConstructor export const createError: CreateError