UNPKG

error-serializer

Version:
404 lines (381 loc) 11.4 kB
/** * Any JSON value. */ type JSONValue = | null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue } interface MinimalErrorObject { message: string [key: PropertyKey]: JSONValue } /** * Error instance converted to a plain object */ export interface ErrorObject extends MinimalErrorObject { name: string stack: string cause?: ErrorObject errors?: ErrorObject[] } /** * `error-serializer` `serialize()` options */ export interface SerializeOptions { /** * Unless this option is `true`, nested errors are also serialized. * They can be inside other errors, plain objects or arrays. * * @default false * * @example * ```js * const error = new Error('example') * error.inner = new Error('inner') * serialize(error).inner // { name: 'Error', message: 'inner', ... } * serialize(error, { shallow: true }).inner // Error: inner ... * ``` */ readonly shallow?: boolean /** * By default, when the argument is not an `Error` instance, it is converted * to one. If this option is `true`, it is kept as is instead. * * @default false * * @example * ```js * serialize('example') // { name: 'Error', message: 'example', ... } * serialize('example', { loose: true }) // 'example' * ``` */ readonly loose?: boolean /** * Only pick specific properties. * * @example * ```js * serialize(error, { include: ['message'] }) // { message: 'example' } * ``` * * @example * ```js * const error = new Error('example') * error.prop = true * * const errorObject = serialize(error, { include: ['name', 'message', 'stack'] }) * console.log(errorObject.prop) // undefined * console.log(errorObject) // { name: 'Error', message: 'example', stack: '...' } * ``` */ readonly include?: readonly string[] /** * Omit specific properties. * * @example * ```js * serialize(error, { exclude: ['stack'] }) // { name: 'Error', message: 'example' } * ``` * * @example * ```js * const error = new Error('example') * * const errorObject = serialize(error, { exclude: ['stack'] }) * console.log(errorObject.stack) // undefined * console.log(errorObject) // { name: 'Error', message: 'example' } * ``` */ readonly exclude?: readonly string[] /** * Transform each error plain object. * * `errorObject` is the error after serialization. It must be directly * mutated. * * `errorInstance` is the error before serialization. * * @example * ```js * const errors = [new Error('test secret')] * errors[0].date = new Date() * * const errorObjects = serialize(errors, { * loose: true, * // Serialize `Date` instances as strings * transformObject: (errorObject) => { * errorObject.date = errorObject.date.toString() * }, * }) * console.log(errorObjects[0].date) // Date string * * const newErrors = parse(errorObjects, { * loose: true, * // Transform error message * transformArgs: (constructorArgs) => { * constructorArgs[0] = constructorArgs[0].replace('secret', '***') * }, * // Parse date strings as `Date` instances * transformInstance: (error) => { * error.date = new Date(error.date) * }, * }) * console.log(newErrors[0].message) // 'test ***' * console.log(newErrors[0].date) // `Date` instance * ``` */ readonly transformObject?: ( errorObject: ErrorObject, errorInstance: Error, ) => void } /** * Convert an `Error` instance into a plain object. * * @example * ```js * const error = new TypeError('example') * const errorObject = serialize(error) * // Plain object: { name: 'TypeError', message: 'example', stack: '...' } * * const errorString = JSON.stringify(errorObject) * const newErrorObject = JSON.parse(errorString) * * const newError = parse(newErrorObject) * // Error instance: 'TypeError: example ...' * ``` */ export function serialize<Value, Options extends SerializeOptions = object>( errorInstance: Value, options?: Options, ): Options['shallow'] extends true ? SerializeShallow<SerializeNormalize<Value, Options>> : SerializeDeep<SerializeNormalize<Value, Options>> type SerializeNormalize< Value, Options extends SerializeOptions, > = Options['loose'] extends true ? Value : Value extends Error ? Value : Error type SerializeShallow<Value> = Value extends Error ? ErrorObject & { [Key in keyof Value as Value[Key] extends ErrorObject[Key] ? Key : never]: Value[Key] } : Value type SerializeDeep<Value> = Value extends Error ? ErrorObject & { [Key in keyof Value as SerializeDeep<Value[Key]> extends ErrorObject[Key] ? Key : never]: SerializeDeep<Value[Key]> } : Value extends object ? { [Key in keyof Value]: SerializeDeep<Value[Key]> } : Value // `Error` is both a `CallableFunction` and a `NewableFunction`, which makes // `typeof Error` not work as expected. type ErrorClass = new (message: string) => Error /** * `error-serializer` `parse()` options */ export interface ParseOptions { /** * Unless this option is `true`, nested error plain objects are also parsed. * They can be inside other errors, plain objects or arrays. * * @default false * * @example * ```js * const error = new Error('example') * error.inner = new Error('inner') * const errorObject = serialize(error) * * parse(errorObject).inner // Error: inner ... * parse(errorObject, { shallow: true }).inner // { name: 'Error', message: ... } * ``` */ readonly shallow?: boolean /** * By default, when the argument is not an error plain object, it is * converted to one. If this option is `true`, it is kept as is instead. * * @default false * * @example * ```js * parse('example') // Error: example * parse('example', { loose: true }) // 'example' * ``` */ readonly loose?: boolean /** * Custom error classes to keep when parsing. * * - Each key is an `errorObject.name` * - Each value is the error class to use * * @example * ```js * const errorObject = serialize(new CustomError('example')) * // `CustomError` class is kept * const error = parse(errorObject, { classes: { CustomError } }) * // Map `CustomError` to another class * const otherError = parse(errorObject, { classes: { CustomError: TypeError } }) * ``` */ readonly classes?: { [ErrorClassName: string]: ErrorClass } /** * Transform the arguments passed to each `new Error()`. * * `constructorArgs` is the array of arguments. Usually, `constructorArgs[0]` * is the * [error message](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/message) * and `constructorArgs[1]` is the * [constructor options object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Error#parameters). * `constructorArgs` must be directly mutated. * * `errorObject` is the error before parsing. * * @example * ```js * const errors = [new Error('test secret')] * errors[0].date = new Date() * * const errorObjects = serialize(errors, { * loose: true, * // Serialize `Date` instances as strings * transformObject: (errorObject) => { * errorObject.date = errorObject.date.toString() * }, * }) * console.log(errorObjects[0].date) // Date string * * const newErrors = parse(errorObjects, { * loose: true, * // Transform error message * transformArgs: (constructorArgs) => { * constructorArgs[0] = constructorArgs[0].replace('secret', '***') * }, * // Parse date strings as `Date` instances * transformInstance: (error) => { * error.date = new Date(error.date) * }, * }) * console.log(newErrors[0].message) // 'test ***' * console.log(newErrors[0].date) // `Date` instance * ``` */ readonly transformArgs?: ( constructorArgs: unknown[], errorObject: ErrorObject, ErrorClass: ErrorClass, ) => void /** * Transform each `Error` instance. * * `errorInstance` is the error after parsing. It must be directly mutated. * * `errorObject` is the error before parsing. * * @example * ```js * const errors = [new Error('test secret')] * errors[0].date = new Date() * * const errorObjects = serialize(errors, { * loose: true, * // Serialize `Date` instances as strings * transformObject: (errorObject) => { * errorObject.date = errorObject.date.toString() * }, * }) * console.log(errorObjects[0].date) // Date string * * const newErrors = parse(errorObjects, { * loose: true, * // Transform error message * transformArgs: (constructorArgs) => { * constructorArgs[0] = constructorArgs[0].replace('secret', '***') * }, * // Parse date strings as `Date` instances * transformInstance: (error) => { * error.date = new Date(error.date) * }, * }) * console.log(newErrors[0].message) // 'test ***' * console.log(newErrors[0].date) // `Date` instance * ``` */ readonly transformInstance?: ( errorInstance: Error, errorObject: ErrorObject, ) => void } /** * Convert an error plain object into an `Error` instance. * * @example * ```js * const error = new TypeError('example') * const errorObject = serialize(error) * // Plain object: { name: 'TypeError', message: 'example', stack: '...' } * * const errorString = JSON.stringify(errorObject) * const newErrorObject = JSON.parse(errorString) * * const newError = parse(newErrorObject) * // Error instance: 'TypeError: example ...' * ``` */ export function parse<Value, Options extends ParseOptions = object>( errorObject: Value, options?: Options, ): Options['shallow'] extends true ? ParseNormalize<ParseShallow<Value, Options>, Options> : ParseNormalize<ParseDeep<Value, Options>, Options> type ParseNormalize< Value, Options extends ParseOptions, > = Options['loose'] extends true ? Value : Value extends Error ? Value : Error type ParseShallow< Value, Options extends ParseOptions, > = Value extends MinimalErrorObject ? ParsedError<Value, Options> & { [Key in keyof Value as Value[Key] extends ( Key extends keyof ParsedError<Value, Options> ? ParsedError<Value, Options>[Key] : unknown ) ? Key : never]: Value[Key] } : Value type ParseDeep< Value, Options extends ParseOptions, > = Value extends MinimalErrorObject ? ParsedError<Value, Options> & { [Key in keyof Value as Key extends keyof ParsedError<Value, Options> ? ParseDeep<Value[Key], Options> extends ParsedError< Value, Options >[Key] ? Key : never : Key]: ParseDeep<Value[Key], Options> } : Value extends object ? { [Key in keyof Value]: ParseDeep<Value[Key], Options> } : Value type ParsedError< ErrorObjectArg extends MinimalErrorObject, Options extends ParseOptions, > = ErrorObjectArg extends { name: string } ? NonNullable<Options['classes']>[ErrorObjectArg['name']] extends ErrorClass ? InstanceType<NonNullable<Options['classes']>[ErrorObjectArg['name']]> : Error : Error