From 8fcd82922900f42d51ed1461926fe9f851091978 Mon Sep 17 00:00:00 2001 From: Ankit raj sharma Date: Tue, 29 Sep 2026 15:11:25 +0530 Subject: [PATCH 1/2] feat: support array and object (messageParams) input (#117) --- index.js | 39 +++++++++++++++++++++++++++++++++++---- test/index.test.js | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 4d8da8e..06af315 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,15 @@ const { format } = require('node:util') +const formatSpecifierRegex = /%[sdifjjoOc]/g + +function countFormatSpecifiers (str) { + if (typeof str !== 'string') return 0 + 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 +32,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 +43,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..1a22508 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -230,3 +230,37 @@ 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) +}) + From dc9a4842379fc4ab0bcecbce12cd020bcd28e1f2 Mon Sep 17 00:00:00 2001 From: Ankit raj sharma Date: Wed, 30 Sep 2026 15:56:58 +0530 Subject: [PATCH 2/2] fix(ci): fix linting trailing lines and achieve 100% branch coverage Signed-off-by: Ankit raj sharma --- index.js | 1 - test/index.test.js | 9 +++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 06af315..f674db6 100644 --- a/index.js +++ b/index.js @@ -5,7 +5,6 @@ const { format } = require('node:util') const formatSpecifierRegex = /%[sdifjjoOc]/g function countFormatSpecifiers (str) { - if (typeof str !== 'string') return 0 const cleaned = str.replace(/%%/g, '') const matches = cleaned.match(formatSpecifierRegex) return matches ? matches.length : 0 diff --git a/test/index.test.js b/test/index.test.js index 1a22508..ab79ac0 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -264,3 +264,12 @@ test('Create error with object input containing only cause when format parameter 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') +})