@fastify/error
Advanced tools
| 'use strict' | ||
| const cp = require('node:child_process') | ||
| const fs = require('node:fs') | ||
| const path = require('node:path') | ||
| const os = require('node:os') | ||
| const test = require('node:test') | ||
| const { createError, FastifyError } = require('..') | ||
| test('Readme: All errors created with `createError` will be instances of the base error constructor you provided, or `Error` if none was provided.', (t) => { | ||
| t.plan(3) | ||
| const CustomError = createError('ERROR_CODE', 'Hello %s', 500, TypeError) | ||
| const customError = new CustomError('world') | ||
| t.assert.ok(customError instanceof CustomError) | ||
| t.assert.ok(customError instanceof TypeError) | ||
| t.assert.ok(customError instanceof Error) | ||
| }) | ||
| test('Readme: All instantiated errors will be instances of the `FastifyError` class. The `FastifyError` class can be required from the module directly.', (t) => { | ||
| t.plan(1) | ||
| const CustomError = createError('ERROR_CODE', 'Hello %s', 500, TypeError) | ||
| const customError = new CustomError('world') | ||
| t.assert.ok(customError instanceof FastifyError) | ||
| }) | ||
| test('Readme: It is possible to create a `FastifyError` that extends another `FastifyError`, created by `createError`, while instanceof working correctly.', (t) => { | ||
| t.plan(5) | ||
| const CustomError = createError('ERROR_CODE', 'Hello %s', 500, TypeError) | ||
| const ChildCustomError = createError('CHILD_ERROR_CODE', 'Hello %s', 500, CustomError) | ||
| const customError = new ChildCustomError('world') | ||
| t.assert.ok(customError instanceof ChildCustomError) | ||
| t.assert.ok(customError instanceof CustomError) | ||
| t.assert.ok(customError instanceof FastifyError) | ||
| t.assert.ok(customError instanceof TypeError) | ||
| t.assert.ok(customError instanceof Error) | ||
| }) | ||
| test('Readme: Changing the code of an instantiated Error will not change the result of the `instanceof` operator.', (t) => { | ||
| t.plan(3) | ||
| const CustomError = createError('ERROR_CODE', 'Hello %s', 500, TypeError) | ||
| const AnotherCustomError = createError('ANOTHER_ERROR_CODE', 'Hello %s', 500, CustomError) | ||
| const customError = new CustomError('world') | ||
| customError.code = 'ANOTHER_ERROR_CODE' | ||
| t.assert.ok(customError instanceof CustomError) | ||
| t.assert.ok(customError instanceof AnotherCustomError === false) | ||
| t.assert.ok(customError instanceof FastifyError) | ||
| }) | ||
| test('check if createError creates an Error which is instanceof Error', (t) => { | ||
| t.plan(3) | ||
| const CustomFastifyError = createError('CODE', 'Not available') | ||
| const err = CustomFastifyError() | ||
| t.assert.ok(err instanceof Error) | ||
| t.assert.ok(err instanceof SyntaxError === false) | ||
| t.assert.ok(err instanceof TypeError === false) | ||
| }) | ||
| test('check if createError creates an Error which is instanceof FastifyError', (t) => { | ||
| t.plan(4) | ||
| const CustomFastifyError = createError('CODE', 'Not available') | ||
| const err = CustomFastifyError() | ||
| t.assert.ok(err instanceof Error) | ||
| t.assert.ok(err instanceof FastifyError) | ||
| t.assert.ok(err instanceof SyntaxError === false) | ||
| t.assert.ok(err instanceof TypeError === false) | ||
| }) | ||
| test('check if createError creates an Error with the right BaseConstructor', (t) => { | ||
| t.plan(2) | ||
| const CustomFastifyError = createError('CODE', 'Not available', 500, TypeError) | ||
| const err = CustomFastifyError() | ||
| t.assert.ok(err instanceof Error) | ||
| t.assert.ok(err instanceof TypeError) | ||
| }) | ||
| test('check if createError creates an Error with the right BaseConstructor, which is a FastifyError', (t) => { | ||
| t.plan(6) | ||
| const BaseFastifyError = createError('CODE', 'Not available', 500, TypeError) | ||
| const CustomFastifyError = createError('CODE', 'Not available', 500, BaseFastifyError) | ||
| const err = CustomFastifyError() | ||
| t.assert.ok(err instanceof Error) | ||
| t.assert.ok(err instanceof TypeError) | ||
| t.assert.ok(err instanceof FastifyError) | ||
| t.assert.ok(err instanceof BaseFastifyError) | ||
| t.assert.ok(err instanceof CustomFastifyError) | ||
| t.assert.ok(err instanceof SyntaxError === false) | ||
| }) | ||
| // for more information see https://github.com/fastify/fastify-error/pull/86#issuecomment-1301466407 | ||
| test('ensure that instanceof works accross different installations of the fastify-error module', async (t) => { | ||
| const assertsPlanned = 5 | ||
| t.plan(assertsPlanned) | ||
| // We need to create a test environment where fastify-error is installed in two different locations | ||
| // and then we will check if the error created in one location is instanceof the error created in the other location | ||
| // This is done by creating a test directory with the following structure: | ||
| // / | ||
| // ├── index.js | ||
| // └── node_modules/ | ||
| // ├── fastify-error/ | ||
| // │ └── index.js | ||
| // └── dep/ | ||
| // ├── index.js | ||
| // └── node_modules/ | ||
| // └── fastify-error/ | ||
| // └── index.js | ||
| const testDirectoryPrefix = 'fastify-error-instanceof-test-' | ||
| const testCwd = path.resolve(os.tmpdir(), `${testDirectoryPrefix}${Math.random().toString(36).substring(2, 15)}`) | ||
| fs.mkdirSync(testCwd, { recursive: true }) | ||
| // Create the index.js. It will be executed as a forked process, so we need to | ||
| // use process.send to send messages back to the parent process. | ||
| fs.writeFileSync(path.resolve(testCwd, 'index.js'), ` | ||
| 'use strict' | ||
| const path = require('node:path') | ||
| const { createError, FastifyError } = require('fastify-error') | ||
| const { foo } = require('dep') | ||
| const actualPathOfFastifyError = require.resolve('fastify-error') | ||
| const expectedPathOfFastifyError = path.resolve('node_modules', 'fastify-error', 'index.js') | ||
| // Ensure that fastify-error is required from the node_modules directory of the test-project | ||
| if (actualPathOfFastifyError !== expectedPathOfFastifyError) { | ||
| console.error('actualPathOfFastifyError', actualPathOfFastifyError) | ||
| console.error('expectedPathOfFastifyError', expectedPathOfFastifyError) | ||
| throw new Error('fastify-error should be required from the node_modules directory of the test-project') | ||
| } | ||
| const Boom = createError('Boom', 'Boom', 500) | ||
| const ChildBoom = createError('ChildBoom', 'Boom', 500, Boom) | ||
| const NotChildBoom = createError('NotChildBoom', 'NotChildBoom', 500, Boom) | ||
| try { | ||
| foo() | ||
| } catch (err) { | ||
| process.send(err instanceof Error) | ||
| process.send(err instanceof FastifyError) | ||
| process.send(err instanceof NotChildBoom) | ||
| process.send(err instanceof Boom) | ||
| process.send(err instanceof ChildBoom) | ||
| } | ||
| `) | ||
| // Create /node_modules/fastify-error directory | ||
| // Copy the index.js file to the fastify-error directory | ||
| fs.mkdirSync(path.resolve(testCwd, 'node_modules', 'fastify-error'), { recursive: true }) | ||
| fs.copyFileSync(path.resolve(process.cwd(), 'index.js'), path.resolve(testCwd, 'node_modules', 'fastify-error', 'index.js')) | ||
| // Create /node_modules/dep/node_modules/fastify-error directory | ||
| // Copy the index.js to the fastify-error directory | ||
| fs.mkdirSync(path.resolve(testCwd, 'node_modules', 'dep', 'node_modules', 'fastify-error'), { recursive: true }) | ||
| fs.copyFileSync(path.resolve(process.cwd(), 'index.js'), path.resolve(testCwd, 'node_modules', 'dep', 'node_modules', 'fastify-error', 'index.js')) | ||
| // Create /node_modules/dep/index.js. It will export a function foo which will | ||
| // throw an error when called. The error will be an instance of ChildBoom, created | ||
| // by the fastify-error module in the node_modules directory of dep. | ||
| fs.writeFileSync(path.resolve(testCwd, 'node_modules', 'dep', 'index.js'), ` | ||
| 'use strict' | ||
| const path = require('node:path') | ||
| const { createError } = require('fastify-error') | ||
| const actualPathOfFastifyError = require.resolve('fastify-error') | ||
| const expectedPathOfFastifyError = path.resolve('node_modules', 'dep', 'node_modules', 'fastify-error', 'index.js') | ||
| // Ensure that fastify-error is required from the node_modules directory of the test-project | ||
| if (actualPathOfFastifyError !== expectedPathOfFastifyError) { | ||
| console.error('actualPathOfFastifyError', actualPathOfFastifyError) | ||
| console.error('expectedPathOfFastifyError', expectedPathOfFastifyError) | ||
| throw new Error('fastify-error should be required from the node_modules directory of dep') | ||
| } | ||
| const Boom = createError('Boom', 'Boom', 500) | ||
| const ChildBoom = createError('ChildBoom', 'Boom', 500, Boom) | ||
| module.exports.foo = function foo () { | ||
| throw new ChildBoom('foo go Boom') | ||
| } | ||
| `) | ||
| const finishedPromise = { | ||
| promise: undefined, | ||
| reject: undefined, | ||
| resolve: undefined, | ||
| } | ||
| finishedPromise.promise = new Promise((resolve, reject) => { | ||
| finishedPromise.resolve = resolve | ||
| finishedPromise.reject = reject | ||
| }) | ||
| const child = cp.fork(path.resolve(testCwd, 'index.js'), { | ||
| cwd: testCwd, | ||
| stdio: 'inherit', | ||
| env: { | ||
| ...process.env, | ||
| NODE_OPTIONS: '--no-warnings' | ||
| }, | ||
| }) | ||
| let messageCount = 0 | ||
| child.on('message', message => { | ||
| try { | ||
| switch (messageCount) { | ||
| case 0: | ||
| t.assert.strictEqual(message, true, 'instanceof Error') | ||
| break | ||
| case 1: | ||
| t.assert.strictEqual(message, true, 'instanceof FastifyError') | ||
| break | ||
| case 2: | ||
| t.assert.strictEqual(message, false, 'instanceof NotChildBoom') | ||
| break | ||
| case 3: | ||
| t.assert.strictEqual(message, true, 'instanceof Boom') | ||
| break | ||
| case 4: | ||
| t.assert.strictEqual(message, true, 'instanceof ChildBoom') | ||
| break | ||
| } | ||
| if (++messageCount === assertsPlanned) { | ||
| finishedPromise.resolve() | ||
| } | ||
| } catch (err) { | ||
| finishedPromise.reject(err) | ||
| } | ||
| }) | ||
| child.on('error', err => { | ||
| finishedPromise.reject(err) | ||
| }) | ||
| await finishedPromise.promise | ||
| // Cleanup | ||
| // As we are creating the test-setup on the fly in the /tmp directory, we can remove it | ||
| // safely when we are done. It is not relevant for the test if the deletion fails. | ||
| try { | ||
| fs.rmSync(testCwd, { recursive: true, force: true }) | ||
| } catch {} | ||
| }) |
@@ -17,2 +17,5 @@ name: CI | ||
| permissions: | ||
| contents: read | ||
| jobs: | ||
@@ -19,0 +22,0 @@ test: |
+45
-0
@@ -9,3 +9,11 @@ 'use strict' | ||
| const FastifyGenericErrorSymbol = Symbol.for('fastify-error-generic') | ||
| function createError (code, message, statusCode = 500, Base = Error, captureStackTrace = createError.captureStackTrace) { | ||
| const shouldCreateFastifyGenericError = code === FastifyGenericErrorSymbol | ||
| if (shouldCreateFastifyGenericError) { | ||
| code = 'FST_ERR' | ||
| } | ||
| if (!code) throw new Error('Fastify error code must not be empty') | ||
@@ -17,2 +25,4 @@ if (!message) throw new Error('Fastify error message must not be empty') | ||
| const FastifySpecificErrorSymbol = Symbol.for(`fastify-error ${code}`) | ||
| function FastifyError (...args) { | ||
@@ -43,5 +53,37 @@ if (!new.target) { | ||
| configurable: true | ||
| }, | ||
| [FastifyGenericErrorSymbol]: { | ||
| value: true, | ||
| enumerable: false, | ||
| writable: false, | ||
| configurable: false | ||
| }, | ||
| [FastifySpecificErrorSymbol]: { | ||
| value: true, | ||
| enumerable: false, | ||
| writable: false, | ||
| configurable: false | ||
| } | ||
| }) | ||
| if (shouldCreateFastifyGenericError) { | ||
| Object.defineProperty(FastifyError, Symbol.hasInstance, { | ||
| value (instance) { | ||
| return instance && instance[FastifyGenericErrorSymbol] | ||
| }, | ||
| configurable: false, | ||
| writable: false, | ||
| enumerable: false | ||
| }) | ||
| } else { | ||
| Object.defineProperty(FastifyError, Symbol.hasInstance, { | ||
| value (instance) { | ||
| return instance && instance[FastifySpecificErrorSymbol] | ||
| }, | ||
| configurable: false, | ||
| writable: false, | ||
| enumerable: false | ||
| }) | ||
| } | ||
| FastifyError.prototype[Symbol.toStringTag] = 'Error' | ||
@@ -56,4 +98,7 @@ | ||
| const FastifyErrorConstructor = createError(FastifyGenericErrorSymbol, 'Fastify Error', 500, Error) | ||
| module.exports = createError | ||
| module.exports.FastifyError = FastifyErrorConstructor | ||
| module.exports.default = createError | ||
| module.exports.createError = createError |
+2
-2
| { | ||
| "name": "@fastify/error", | ||
| "version": "4.1.0", | ||
| "version": "4.2.0", | ||
| "description": "A small utility, used by Fastify itself, for generating consistent error objects across your codebase and plugins.", | ||
@@ -65,3 +65,3 @@ "main": "index.js", | ||
| "neostandard": "^0.12.0", | ||
| "tsd": "^0.31.0" | ||
| "tsd": "^0.32.0" | ||
| }, | ||
@@ -68,0 +68,0 @@ "tsd": { |
+78
-1
@@ -18,3 +18,3 @@ # @fastify/error | ||
| ``` | ||
| ```js | ||
| createError(code, message [, statusCode [, Base [, captureStackTrace]]]) | ||
@@ -62,4 +62,81 @@ ``` | ||
| ### instanceof | ||
| All errors created with `createError` will be instances of the base error constructor you provided, or `Error` if none was provided. | ||
| ```js | ||
| const createError = require('@fastify/error') | ||
| const CustomError = createError('ERROR_CODE', 'Hello %s', 500, TypeError) | ||
| const customError = new CustomError('world') | ||
| console.log(customError instanceof CustomError) // true | ||
| console.log(customError instanceof TypeError) // true | ||
| console.log(customError instanceof Error) // true | ||
| ``` | ||
| All instantiated errors are instances of the `FastifyError` class, which can be required directly from the module. | ||
| ```js | ||
| const { createError, FastifyError } = require('@fastify/error') | ||
| const CustomError = createError('ERROR_CODE', 'Hello %s', 500, TypeError) | ||
| const customError = new CustomError('world') | ||
| console.log(customError instanceof FastifyError) // true | ||
| ``` | ||
| A `FastifyError` created by `createError` can extend another `FastifyError` while maintaining correct `instanceof` behavior. | ||
| ```js | ||
| const { createError, FastifyError } = require('@fastify/error') | ||
| const CustomError = createError('ERROR_CODE', 'Hello %s', 500, TypeError) | ||
| const ChildCustomError = createError('CHILD_ERROR_CODE', 'Hello %s', 500, CustomError) | ||
| const customError = new ChildCustomError('world') | ||
| console.log(customError instanceof ChildCustomError) // true | ||
| console.log(customError instanceof CustomError) // true | ||
| console.log(customError instanceof FastifyError) // true | ||
| console.log(customError instanceof TypeError) // true | ||
| console.log(customError instanceof Error) // true | ||
| ``` | ||
| If `fastify-error` is installed multiple times directly or as a transitive dependency, `instanceof` checks for errors created by `createError` will still work correctly across these installations, as long as their error codes (e.g., `FST_ERR_CUSTOM_ERROR`) are identical. | ||
| ```js | ||
| const { createError, FastifyError } = require('@fastify/error') | ||
| // CustomError from `@fastify/some-plugin` is created with `createError` and | ||
| // has its own `@fastify/error` installation as dependency. CustomError has | ||
| // FST_ERR_CUSTOM_ERROR as code. | ||
| const { CustomError: CustomErrorFromPlugin } = require('@fastify/some-plugin') | ||
| const CustomError = createError('FST_ERR_CUSTOM_ERROR', 'Hello %s', 500) | ||
| const customError = new CustomError('world') | ||
| const customErrorFromPlugin = new CustomErrorFromPlugin('world') | ||
| console.log(customError instanceof CustomError) // true | ||
| console.log(customError instanceof CustomErrorFromPlugin) // true | ||
| console.log(customErrorFromPlugin instanceof CustomError) // true | ||
| console.log(customErrorFromPlugin instanceof CustomErrorFromPlugin) // true | ||
| ``` | ||
| Changing the code of an instantiated Error will not change the result of the `instanceof` operator. | ||
| ```js | ||
| const { createError, FastifyError } = require('@fastify/error') | ||
| const CustomError = createError('ERROR_CODE', 'Hello %s', 500, TypeError) | ||
| const AnotherCustomError = createError('ANOTHER_ERROR_CODE', 'Hello %s', 500, CustomError) | ||
| const customError = new CustomError('world') | ||
| customError.code = 'ANOTHER_ERROR_CODE' | ||
| console.log(customError instanceof CustomError) // true | ||
| console.log(customError instanceof AnotherCustomError) // false | ||
| ``` | ||
| ## License | ||
| Licensed under [MIT](./LICENSE). |
+10
-1
| 'use strict' | ||
| const test = require('node:test') | ||
| const createError = require('..') | ||
| const { createError, FastifyError } = require('..') | ||
@@ -224,1 +224,10 @@ test('Create error with zero parameter', (t) => { | ||
| }) | ||
| test('check if FastifyError is instantiable', (t) => { | ||
| t.plan(2) | ||
| const err = new FastifyError() | ||
| t.assert.ok(err instanceof FastifyError) | ||
| t.assert.ok(err instanceof Error) | ||
| }) |
+2
-0
@@ -43,2 +43,4 @@ declare function createError<C extends string, SC extends number, Arg extends unknown[] = [any?, any?, any?]> ( | ||
| export const FastifyError: FastifyErrorConstructor | ||
| export const createError: CreateError | ||
@@ -45,0 +47,0 @@ export { createError as default } |
@@ -80,1 +80,14 @@ import createError, { FastifyError, FastifyErrorConstructor } from '..' | ||
| CustomErrorWithErrorConstructor({ cause: new Error('Error') }) | ||
| const customErrorWithErrorConstructor = CustomErrorWithErrorConstructor() | ||
| if (customErrorWithErrorConstructor instanceof FastifyError) { | ||
| expectType<'ERROR_CODE'>(customErrorWithErrorConstructor.code) | ||
| expectType<string>(customErrorWithErrorConstructor.message) | ||
| expectType<500>(customErrorWithErrorConstructor.statusCode) | ||
| } | ||
| const error = new FastifyError('ERROR_CODE', 'message', 500) | ||
| if (error instanceof FastifyError) { | ||
| expectType<string>(error.code) | ||
| expectType<string>(error.message) | ||
| expectType<number | undefined>(error.statusCode) | ||
| } |
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
36872
70.3%16
6.67%645
71.09%141
120.31%2
Infinity%