|
| 1 | +export const isUndefined = (obj: any): obj is undefined => typeof obj === 'undefined'; |
| 2 | + |
| 3 | +export const isNil = (val: any): val is null | undefined => isUndefined(val) || val === null; |
| 4 | + |
| 5 | +export const isObject = (fn: any): fn is object => !isNil(fn) && typeof fn === 'object'; |
| 6 | + |
| 7 | +export const isString = (val: any): val is string => typeof val === 'string'; |
| 8 | + |
| 9 | +export const isNumber = (val: any): val is number => typeof val === 'number'; |
| 10 | + |
| 11 | +export interface HttpExceptionOptions { |
| 12 | + /** Original cause of the error */ |
| 13 | + cause?: unknown; |
| 14 | + description?: string; |
| 15 | +} |
| 16 | + |
| 17 | +export const DEFAULT_EXCEPTION_STATUS = 500; |
| 18 | +export const DEFAULT_EXCEPTION_MESSAGE = 'Something went wrong'; |
| 19 | +export const DEFAULT_EXCEPTION_CODE = 'INTERNAL_SERVER_ERROR'; |
| 20 | +export const DEFAULT_EXCEPTION_STATUS_TEXT = 'Internal server error'; |
| 21 | + |
| 22 | +/** Defines the base HTTP exception, which is handled by the default Exceptions Handler. */ |
| 23 | +export class HttpException extends Error { |
| 24 | + /** |
| 25 | + * Instantiate a plain HTTP Exception. |
| 26 | + * |
| 27 | + * @example |
| 28 | + * throw new HttpException('message', HttpStatus.BAD_REQUEST); |
| 29 | + * throw new HttpException('custom message', HttpStatus.BAD_REQUEST, { |
| 30 | + * cause: new Error('Cause Error'), |
| 31 | + * }); |
| 32 | + * |
| 33 | + * @param response String, object describing the error condition or the error cause. |
| 34 | + * @param status HTTP response status code. |
| 35 | + * @param options An object used to add an error cause. Configures error chaining support |
| 36 | + * @usageNotes |
| 37 | + * The constructor arguments define the response and the HTTP response status code. |
| 38 | + * - The `response` argument (required) defines the JSON response body. alternatively, it can also be |
| 39 | + * an error object that is used to define an error [cause](https://nodejs.org/en/blog/release/v16.9.0/#error-cause). |
| 40 | + * - The `status` argument (optional) defines the HTTP Status Code. |
| 41 | + * - The `options` argument (optional) defines additional error options. Currently, it supports the `cause` attribute, |
| 42 | + * and can be used as an alternative way to specify the error cause: `const error = new HttpException('description', 400, { cause: new Error() });` |
| 43 | + * |
| 44 | + * By default, the JSON response body contains two properties: |
| 45 | + * - `statusCode`: the Http Status Code. |
| 46 | + * - `message`: a short description of the HTTP error by default; override this |
| 47 | + * by supplying a string in the `response` parameter. |
| 48 | + * |
| 49 | + * The `status` argument is required, and should be a valid HTTP status code. |
| 50 | + * Best practice is to use the `HttpStatus` enum imported from `nestjs/common`. |
| 51 | + * @see https://nodejs.org/en/blog/release/v16.9.0/#error-cause |
| 52 | + * @see https://github.com/microsoft/TypeScript/issues/45167 |
| 53 | + */ |
| 54 | + constructor( |
| 55 | + public readonly response: string | Record<string, any>, |
| 56 | + status?: number, |
| 57 | + options?: HttpExceptionOptions, |
| 58 | + ) { |
| 59 | + super(); |
| 60 | + |
| 61 | + this.name = this.initName(); |
| 62 | + this.cause = this.initCause(response, options); |
| 63 | + this.code = this.initCode(response); |
| 64 | + this.message = this.initMessage(response); |
| 65 | + this.status = this.initStatus(response, status); |
| 66 | + this.statusText = this.initStatusText(response, this.status); |
| 67 | + } |
| 68 | + |
| 69 | + public readonly cause?: unknown; |
| 70 | + public readonly code?: string; |
| 71 | + public readonly status: number; |
| 72 | + public readonly statusText?: string; |
| 73 | + |
| 74 | + protected initMessage(response: string | Record<string, any>) { |
| 75 | + if (isString(response)) { |
| 76 | + return response; |
| 77 | + } |
| 78 | + |
| 79 | + if (isObject(response) && isString((response as Record<string, any>).message)) { |
| 80 | + return (response as Record<string, any>).message; |
| 81 | + } |
| 82 | + |
| 83 | + if (this.constructor) { |
| 84 | + return this.constructor.name.match(/[A-Z][a-z]+|[0-9]+/g)?.join(' ') ?? 'Error'; |
| 85 | + } |
| 86 | + |
| 87 | + return DEFAULT_EXCEPTION_MESSAGE; |
| 88 | + } |
| 89 | + |
| 90 | + protected initCause(response: string | Record<string, any>, options?: HttpExceptionOptions): unknown { |
| 91 | + if (options?.cause) { |
| 92 | + return options.cause; |
| 93 | + } |
| 94 | + |
| 95 | + if (isObject(response) && isObject((response as Record<string, any>).cause)) { |
| 96 | + return (response as Record<string, any>).cause; |
| 97 | + } |
| 98 | + |
| 99 | + return undefined; |
| 100 | + } |
| 101 | + |
| 102 | + protected initCode(response: string | Record<string, any>): string { |
| 103 | + if (isObject(response) && isString((response as Record<string, any>).code)) { |
| 104 | + return (response as Record<string, any>).code; |
| 105 | + } |
| 106 | + |
| 107 | + return DEFAULT_EXCEPTION_CODE; |
| 108 | + } |
| 109 | + |
| 110 | + protected initName(): string { |
| 111 | + return this.constructor.name; |
| 112 | + } |
| 113 | + |
| 114 | + protected initStatus(response: string | Record<string, any>, status?: number): number { |
| 115 | + if (status) { |
| 116 | + return status; |
| 117 | + } |
| 118 | + |
| 119 | + if (isObject(response) && isNumber((response as Record<string, any>).status)) { |
| 120 | + return (response as Record<string, any>).status; |
| 121 | + } |
| 122 | + |
| 123 | + if (isObject(response) && isNumber((response as Record<string, any>).statusCode)) { |
| 124 | + return (response as Record<string, any>).statusCode; |
| 125 | + } |
| 126 | + |
| 127 | + return DEFAULT_EXCEPTION_STATUS; |
| 128 | + } |
| 129 | + |
| 130 | + protected initStatusText(response: string | Record<string, any>, status?: number): string | undefined { |
| 131 | + if (isObject(response) && isString((response as Record<string, any>).statusText)) { |
| 132 | + return (response as Record<string, any>).statusText; |
| 133 | + } |
| 134 | + |
| 135 | + return status ? undefined : DEFAULT_EXCEPTION_STATUS_TEXT; |
| 136 | + } |
| 137 | +} |
0 commit comments