diff --git a/index.js b/index.js index 4d8da8e..f674db6 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,14 @@ const { format } = require('node:util') +const formatSpecifierRegex = /%[sdifjjoOc]/g + +function countFormatSpecifiers (str) { + const cleaned = str.replace(/%%/g, '') + const matches = cleaned.match(formatSpecifierRegex) + return matches ? matches.length : 0 +} + function toString () { return `${this.name} [${this.code}]: ${this.message}` } @@ -23,6 +31,8 @@ function createError (code, message, statusCode = 500, Base = Error, captureStac const FastifySpecificErrorSymbol = Symbol.for(`fastify-error ${code}`) + const expectedParams = countFormatSpecifiers(message) + function FastifyError (...args) { if (!new.target) { return new FastifyError(...args) @@ -32,12 +42,32 @@ function createError (code, message, statusCode = 500, Base = Error, captureStac this.name = 'FastifyError' this.statusCode = statusCode - const lastElement = args.length - 1 - if (lastElement !== -1 && args[lastElement] && typeof args[lastElement] === 'object' && 'cause' in args[lastElement]) { - this.cause = args.pop().cause + let formatArgs = args + if (args.length === 1 && args[0] && typeof args[0] === 'object' && !(args[0] instanceof Error)) { + if ('messageParams' in args[0] || ('cause' in args[0] && expectedParams > 0)) { + if ('cause' in args[0]) { + this.cause = args[0].cause + } + if ('messageParams' in args[0]) { + formatArgs = Array.isArray(args[0].messageParams) ? args[0].messageParams : [args[0].messageParams] + } else if (expectedParams > 0) { + formatArgs = [] + } + } + } + + if (formatArgs === args) { + const lastElement = formatArgs.length - 1 + if (lastElement !== -1 && formatArgs[lastElement] && typeof formatArgs[lastElement] === 'object' && 'cause' in formatArgs[lastElement]) { + this.cause = formatArgs.pop().cause + } + + if (formatArgs.length === 1 && Array.isArray(formatArgs[0])) { + formatArgs = formatArgs[0] + } } - this.message = format(message, ...args) + this.message = format(message, ...formatArgs) Error.stackTraceLimit && captureStackTrace && Error.captureStackTrace(this, FastifyError) } diff --git a/test/index.test.js b/test/index.test.js index 3ad97f6..ab79ac0 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -230,3 +230,46 @@ test('check if FastifyError is instantiable', (t) => { t.assert.ok(err instanceof FastifyError) t.assert.ok(err instanceof Error) }) + +test('Create error with array of parameters', (t) => { + t.plan(3) + + const NewError = createError('CODE', 'hey %s, I like your %s') + const err = new NewError(['alice', 'attitude']) + t.assert.ok(err instanceof Error) + t.assert.equal(err.name, 'FastifyError') + t.assert.equal(err.message, 'hey alice, I like your attitude') +}) + +test('Create error with object input containing messageParams and cause', (t) => { + t.plan(4) + + const cause = new Error('root cause') + const NewError = createError('CODE', 'hey %s, I like your %s') + const err = new NewError({ messageParams: ['alice', 'attitude'], cause }) + t.assert.ok(err instanceof Error) + t.assert.equal(err.name, 'FastifyError') + t.assert.equal(err.message, 'hey alice, I like your attitude') + t.assert.equal(err.cause, cause) +}) + +test('Create error with object input containing only cause when format parameters exist', (t) => { + t.plan(3) + + const cause = new Error('root cause') + const NewError = createError('CODE', 'hey %s') + const err = new NewError({ cause }) + t.assert.ok(err instanceof Error) + t.assert.equal(err.message, 'hey %s') + t.assert.equal(err.cause, cause) +}) + +test('Create error with object input containing single messageParams (non-array)', (t) => { + t.plan(3) + + const NewError = createError('CODE', 'hey %s') + const err = new NewError({ messageParams: 'alice' }) + t.assert.ok(err instanceof Error) + t.assert.equal(err.name, 'FastifyError') + t.assert.equal(err.message, 'hey alice') +})