@nextrush/errors 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +681 -0
- package/dist/index.d.ts +583 -0
- package/dist/index.js +1021 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/src/__tests__/base.test.ts +180 -0
- package/src/__tests__/factory.test.ts +312 -0
- package/src/__tests__/http-errors.test.ts +458 -0
- package/src/__tests__/middleware.test.ts +385 -0
- package/src/__tests__/validation.test.ts +315 -0
- package/src/base.ts +162 -0
- package/src/factory.ts +205 -0
- package/src/http-errors.ts +409 -0
- package/src/index.ts +101 -0
- package/src/middleware.ts +183 -0
- package/src/validation.ts +210 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/base.ts","../src/http-errors.ts","../src/validation.ts","../src/factory.ts","../src/middleware.ts"],"sourcesContent":["/**\n * @nextrush/errors - Base Error Classes\n *\n * Foundational error classes for NextRush framework.\n *\n * @packageDocumentation\n */\n\nconst V8Error = Error as ErrorConstructor & {\n captureStackTrace?: (targetObject: object, constructorOpt?: Function) => void;\n};\n\n/**\n * Base error class for all NextRush errors\n */\nexport class NextRushError extends Error {\n /** HTTP status code */\n readonly status: number;\n\n /** Error code for programmatic handling */\n readonly code: string;\n\n /** Whether error message is safe to expose to client */\n readonly expose: boolean;\n\n /** Additional error details */\n readonly details?: Record<string, unknown>;\n\n /** Original error that caused this error */\n readonly cause?: unknown;\n\n constructor(\n message: string,\n options: {\n status?: number;\n code?: string;\n expose?: boolean;\n details?: Record<string, unknown>;\n cause?: unknown;\n } = {}\n ) {\n super(message);\n this.name = this.constructor.name;\n this.status = options.status ?? 500;\n this.code = options.code ?? 'INTERNAL_ERROR';\n this.expose = options.expose ?? this.status < 500;\n this.details = options.details;\n this.cause = options.cause;\n\n // Maintain proper stack trace for V8\n // Skip for common client errors (4xx with expose=true) to reduce overhead.\n // These are expected control-flow errors, not bugs — stack traces add cost\n // without diagnostic value in production.\n if (V8Error.captureStackTrace && !(this.expose && this.status < 500)) {\n V8Error.captureStackTrace(this, this.constructor);\n }\n }\n\n /**\n * Convert error to JSON representation\n */\n toJSON(): Record<string, unknown> {\n const json: Record<string, unknown> = {\n error: this.name,\n message: this.expose ? this.message : 'Internal Server Error',\n code: this.code,\n status: this.status,\n };\n\n if (this.expose && this.details) {\n json.details = this.details;\n }\n\n return json;\n }\n\n /**\n * Create a response-safe version of the error\n */\n toResponse(): { status: number; body: Record<string, unknown> } {\n return {\n status: this.status,\n body: this.toJSON(),\n };\n }\n}\n\n/**\n * Default HTTP status messages\n */\nconst HTTP_STATUS_MESSAGES: Record<number, string> = {\n 400: 'Bad Request',\n 401: 'Unauthorized',\n 402: 'Payment Required',\n 403: 'Forbidden',\n 404: 'Not Found',\n 405: 'Method Not Allowed',\n 406: 'Not Acceptable',\n 407: 'Proxy Authentication Required',\n 408: 'Request Timeout',\n 409: 'Conflict',\n 410: 'Gone',\n 411: 'Length Required',\n 412: 'Precondition Failed',\n 413: 'Payload Too Large',\n 414: 'URI Too Long',\n 415: 'Unsupported Media Type',\n 416: 'Range Not Satisfiable',\n 417: 'Expectation Failed',\n 418: \"I'm a Teapot\",\n 422: 'Unprocessable Entity',\n 423: 'Locked',\n 424: 'Failed Dependency',\n 425: 'Too Early',\n 426: 'Upgrade Required',\n 428: 'Precondition Required',\n 429: 'Too Many Requests',\n 431: 'Request Header Fields Too Large',\n 451: 'Unavailable For Legal Reasons',\n 500: 'Internal Server Error',\n 501: 'Not Implemented',\n 502: 'Bad Gateway',\n 503: 'Service Unavailable',\n 504: 'Gateway Timeout',\n 505: 'HTTP Version Not Supported',\n 506: 'Variant Also Negotiates',\n 507: 'Insufficient Storage',\n 508: 'Loop Detected',\n 510: 'Not Extended',\n 511: 'Network Authentication Required',\n};\n\n/**\n * Get default message for an HTTP status code\n */\nexport function getHttpStatusMessage(status: number): string {\n return HTTP_STATUS_MESSAGES[status] ?? `HTTP Error ${status}`;\n}\n\n/**\n * Base class for HTTP errors\n */\nexport class HttpError extends NextRushError {\n constructor(\n status: number,\n message?: string,\n options: {\n code?: string;\n expose?: boolean;\n details?: Record<string, unknown>;\n cause?: unknown;\n } = {}\n ) {\n super(message ?? getHttpStatusMessage(status), {\n status,\n code: options.code ?? `HTTP_${status}`,\n expose: options.expose ?? status < 500,\n details: options.details,\n cause: options.cause,\n });\n }\n}\n","/**\n * @nextrush/errors - HTTP Error Classes\n *\n * Standard HTTP error classes for common status codes.\n *\n * @packageDocumentation\n */\n\nimport { HttpError } from './base';\n\n/**\n * Options for creating HTTP errors\n */\nexport interface HttpErrorOptions {\n code?: string;\n expose?: boolean;\n details?: Record<string, unknown>;\n cause?: unknown;\n}\n\n// =============================================================================\n// 4xx Client Errors\n// =============================================================================\n\n/**\n * 400 Bad Request - Invalid syntax or malformed request\n */\nexport class BadRequestError extends HttpError {\n constructor(message = 'Bad Request', options: HttpErrorOptions = {}) {\n super(400, message, { code: 'BAD_REQUEST', ...options });\n }\n}\n\n/**\n * 401 Unauthorized - Authentication required\n */\nexport class UnauthorizedError extends HttpError {\n constructor(message = 'Unauthorized', options: HttpErrorOptions = {}) {\n super(401, message, { code: 'UNAUTHORIZED', ...options });\n }\n}\n\n/**\n * 402 Payment Required - Payment needed\n */\nexport class PaymentRequiredError extends HttpError {\n constructor(message = 'Payment Required', options: HttpErrorOptions = {}) {\n super(402, message, { code: 'PAYMENT_REQUIRED', ...options });\n }\n}\n\n/**\n * 403 Forbidden - Access denied\n */\nexport class ForbiddenError extends HttpError {\n constructor(message = 'Forbidden', options: HttpErrorOptions = {}) {\n super(403, message, { code: 'FORBIDDEN', ...options });\n }\n}\n\n/**\n * 404 Not Found - Resource not found\n */\nexport class NotFoundError extends HttpError {\n constructor(message = 'Not Found', options: HttpErrorOptions = {}) {\n super(404, message, { code: 'NOT_FOUND', ...options });\n }\n}\n\n/**\n * 405 Method Not Allowed - HTTP method not supported\n */\nexport class MethodNotAllowedError extends HttpError {\n readonly allowedMethods: string[];\n\n constructor(\n allowedMethods: string[] = [],\n message = 'Method Not Allowed',\n options: HttpErrorOptions = {}\n ) {\n super(405, message, {\n code: 'METHOD_NOT_ALLOWED',\n details: { allowedMethods },\n ...options,\n });\n this.allowedMethods = allowedMethods;\n }\n}\n\n/**\n * 406 Not Acceptable - Cannot produce acceptable response\n */\nexport class NotAcceptableError extends HttpError {\n constructor(message = 'Not Acceptable', options: HttpErrorOptions = {}) {\n super(406, message, { code: 'NOT_ACCEPTABLE', ...options });\n }\n}\n\n/**\n * 407 Proxy Authentication Required\n */\nexport class ProxyAuthRequiredError extends HttpError {\n constructor(message = 'Proxy Authentication Required', options: HttpErrorOptions = {}) {\n super(407, message, { code: 'PROXY_AUTH_REQUIRED', ...options });\n }\n}\n\n/**\n * 408 Request Timeout - Client took too long\n */\nexport class RequestTimeoutError extends HttpError {\n constructor(message = 'Request Timeout', options: HttpErrorOptions = {}) {\n super(408, message, { code: 'REQUEST_TIMEOUT', ...options });\n }\n}\n\n/**\n * 409 Conflict - Request conflicts with current state\n */\nexport class ConflictError extends HttpError {\n constructor(message = 'Conflict', options: HttpErrorOptions = {}) {\n super(409, message, { code: 'CONFLICT', ...options });\n }\n}\n\n/**\n * 410 Gone - Resource permanently deleted\n */\nexport class GoneError extends HttpError {\n constructor(message = 'Gone', options: HttpErrorOptions = {}) {\n super(410, message, { code: 'GONE', ...options });\n }\n}\n\n/**\n * 411 Length Required - Content-Length header required\n */\nexport class LengthRequiredError extends HttpError {\n constructor(message = 'Length Required', options: HttpErrorOptions = {}) {\n super(411, message, { code: 'LENGTH_REQUIRED', ...options });\n }\n}\n\n/**\n * 412 Precondition Failed - Precondition in headers not met\n */\nexport class PreconditionFailedError extends HttpError {\n constructor(message = 'Precondition Failed', options: HttpErrorOptions = {}) {\n super(412, message, { code: 'PRECONDITION_FAILED', ...options });\n }\n}\n\n/**\n * 413 Payload Too Large - Request body too large\n */\nexport class PayloadTooLargeError extends HttpError {\n constructor(message = 'Payload Too Large', options: HttpErrorOptions = {}) {\n super(413, message, { code: 'PAYLOAD_TOO_LARGE', ...options });\n }\n}\n\n/**\n * 414 URI Too Long - Request URI too long\n */\nexport class UriTooLongError extends HttpError {\n constructor(message = 'URI Too Long', options: HttpErrorOptions = {}) {\n super(414, message, { code: 'URI_TOO_LONG', ...options });\n }\n}\n\n/**\n * 415 Unsupported Media Type - Content type not supported\n */\nexport class UnsupportedMediaTypeError extends HttpError {\n constructor(message = 'Unsupported Media Type', options: HttpErrorOptions = {}) {\n super(415, message, { code: 'UNSUPPORTED_MEDIA_TYPE', ...options });\n }\n}\n\n/**\n * 416 Range Not Satisfiable - Cannot satisfy Range header\n */\nexport class RangeNotSatisfiableError extends HttpError {\n constructor(message = 'Range Not Satisfiable', options: HttpErrorOptions = {}) {\n super(416, message, { code: 'RANGE_NOT_SATISFIABLE', ...options });\n }\n}\n\n/**\n * 417 Expectation Failed - Cannot meet Expect header\n */\nexport class ExpectationFailedError extends HttpError {\n constructor(message = 'Expectation Failed', options: HttpErrorOptions = {}) {\n super(417, message, { code: 'EXPECTATION_FAILED', ...options });\n }\n}\n\n/**\n * 418 I'm a Teapot - RFC 2324\n */\nexport class ImATeapotError extends HttpError {\n constructor(message = \"I'm a teapot\", options: HttpErrorOptions = {}) {\n super(418, message, { code: 'IM_A_TEAPOT', ...options });\n }\n}\n\n/**\n * 422 Unprocessable Entity - Semantic errors\n */\nexport class UnprocessableEntityError extends HttpError {\n constructor(message = 'Unprocessable Entity', options: HttpErrorOptions = {}) {\n super(422, message, { code: 'UNPROCESSABLE_ENTITY', ...options });\n }\n}\n\n/**\n * 423 Locked - Resource is locked\n */\nexport class LockedError extends HttpError {\n constructor(message = 'Locked', options: HttpErrorOptions = {}) {\n super(423, message, { code: 'LOCKED', ...options });\n }\n}\n\n/**\n * 424 Failed Dependency - Dependent request failed\n */\nexport class FailedDependencyError extends HttpError {\n constructor(message = 'Failed Dependency', options: HttpErrorOptions = {}) {\n super(424, message, { code: 'FAILED_DEPENDENCY', ...options });\n }\n}\n\n/**\n * 425 Too Early - Request replayed too early\n */\nexport class TooEarlyError extends HttpError {\n constructor(message = 'Too Early', options: HttpErrorOptions = {}) {\n super(425, message, { code: 'TOO_EARLY', ...options });\n }\n}\n\n/**\n * 426 Upgrade Required - Client should upgrade protocol\n */\nexport class UpgradeRequiredError extends HttpError {\n constructor(message = 'Upgrade Required', options: HttpErrorOptions = {}) {\n super(426, message, { code: 'UPGRADE_REQUIRED', ...options });\n }\n}\n\n/**\n * 428 Precondition Required - Origin server requires conditional request\n */\nexport class PreconditionRequiredError extends HttpError {\n constructor(message = 'Precondition Required', options: HttpErrorOptions = {}) {\n super(428, message, { code: 'PRECONDITION_REQUIRED', ...options });\n }\n}\n\n/**\n * 429 Too Many Requests - Rate limit exceeded\n */\nexport class TooManyRequestsError extends HttpError {\n readonly retryAfter?: number;\n\n constructor(\n message = 'Too Many Requests',\n options: HttpErrorOptions & { retryAfter?: number } = {}\n ) {\n super(429, message, {\n code: 'TOO_MANY_REQUESTS',\n details: options.retryAfter ? { retryAfter: options.retryAfter } : undefined,\n ...options,\n });\n this.retryAfter = options.retryAfter;\n }\n}\n\n/**\n * 431 Request Header Fields Too Large\n */\nexport class RequestHeaderFieldsTooLargeError extends HttpError {\n constructor(message = 'Request Header Fields Too Large', options: HttpErrorOptions = {}) {\n super(431, message, { code: 'REQUEST_HEADER_FIELDS_TOO_LARGE', ...options });\n }\n}\n\n/**\n * 451 Unavailable For Legal Reasons\n */\nexport class UnavailableForLegalReasonsError extends HttpError {\n constructor(message = 'Unavailable For Legal Reasons', options: HttpErrorOptions = {}) {\n super(451, message, { code: 'UNAVAILABLE_FOR_LEGAL_REASONS', ...options });\n }\n}\n\n// =============================================================================\n// 5xx Server Errors\n// =============================================================================\n\n/**\n * 500 Internal Server Error - Generic server error\n */\nexport class InternalServerError extends HttpError {\n constructor(message = 'Internal Server Error', options: HttpErrorOptions = {}) {\n super(500, message, { code: 'INTERNAL_SERVER_ERROR', expose: false, ...options });\n }\n}\n\n/**\n * 501 Not Implemented - Feature not implemented\n */\nexport class NotImplementedError extends HttpError {\n constructor(message = 'Not Implemented', options: HttpErrorOptions = {}) {\n super(501, message, { code: 'NOT_IMPLEMENTED', expose: false, ...options });\n }\n}\n\n/**\n * 502 Bad Gateway - Invalid upstream response\n */\nexport class BadGatewayError extends HttpError {\n constructor(message = 'Bad Gateway', options: HttpErrorOptions = {}) {\n super(502, message, { code: 'BAD_GATEWAY', expose: false, ...options });\n }\n}\n\n/**\n * 503 Service Unavailable - Server temporarily unavailable\n */\nexport class ServiceUnavailableError extends HttpError {\n readonly retryAfter?: number;\n\n constructor(\n message = 'Service Unavailable',\n options: HttpErrorOptions & { retryAfter?: number } = {}\n ) {\n super(503, message, {\n code: 'SERVICE_UNAVAILABLE',\n expose: false,\n ...options,\n });\n this.retryAfter = options.retryAfter;\n }\n}\n\n/**\n * 504 Gateway Timeout - Upstream timeout\n */\nexport class GatewayTimeoutError extends HttpError {\n constructor(message = 'Gateway Timeout', options: HttpErrorOptions = {}) {\n super(504, message, { code: 'GATEWAY_TIMEOUT', expose: false, ...options });\n }\n}\n\n/**\n * 505 HTTP Version Not Supported\n */\nexport class HttpVersionNotSupportedError extends HttpError {\n constructor(message = 'HTTP Version Not Supported', options: HttpErrorOptions = {}) {\n super(505, message, { code: 'HTTP_VERSION_NOT_SUPPORTED', expose: false, ...options });\n }\n}\n\n/**\n * 506 Variant Also Negotiates\n */\nexport class VariantAlsoNegotiatesError extends HttpError {\n constructor(message = 'Variant Also Negotiates', options: HttpErrorOptions = {}) {\n super(506, message, { code: 'VARIANT_ALSO_NEGOTIATES', expose: false, ...options });\n }\n}\n\n/**\n * 507 Insufficient Storage\n */\nexport class InsufficientStorageError extends HttpError {\n constructor(message = 'Insufficient Storage', options: HttpErrorOptions = {}) {\n super(507, message, { code: 'INSUFFICIENT_STORAGE', expose: false, ...options });\n }\n}\n\n/**\n * 508 Loop Detected\n */\nexport class LoopDetectedError extends HttpError {\n constructor(message = 'Loop Detected', options: HttpErrorOptions = {}) {\n super(508, message, { code: 'LOOP_DETECTED', expose: false, ...options });\n }\n}\n\n/**\n * 510 Not Extended\n */\nexport class NotExtendedError extends HttpError {\n constructor(message = 'Not Extended', options: HttpErrorOptions = {}) {\n super(510, message, { code: 'NOT_EXTENDED', expose: false, ...options });\n }\n}\n\n/**\n * 511 Network Authentication Required\n */\nexport class NetworkAuthRequiredError extends HttpError {\n constructor(message = 'Network Authentication Required', options: HttpErrorOptions = {}) {\n super(511, message, { code: 'NETWORK_AUTH_REQUIRED', expose: false, ...options });\n }\n}\n","/**\n * @nextrush/errors - Validation Error Classes\n *\n * Specialized errors for input validation.\n *\n * @packageDocumentation\n */\n\nimport { NextRushError } from './base';\n\n/**\n * Single validation issue\n */\nexport interface ValidationIssue {\n /** Field path (e.g., 'user.email' or 'items[0].name') */\n path: string;\n /** Error message for this field */\n message: string;\n /** Validation rule that failed */\n rule?: string;\n /** Expected value or constraint */\n expected?: unknown;\n /** Actual value received */\n received?: unknown;\n}\n\n/**\n * Validation error with multiple issues\n */\nexport class ValidationError extends NextRushError {\n /** List of validation issues */\n readonly issues: ValidationIssue[];\n\n constructor(issues: ValidationIssue[], message = 'Validation failed') {\n super(message, {\n status: 400,\n code: 'VALIDATION_ERROR',\n expose: true,\n });\n this.issues = issues;\n }\n\n /**\n * Create from a single field error\n */\n static fromField(path: string, message: string, rule?: string): ValidationError {\n return new ValidationError([{ path, message, rule }]);\n }\n\n /**\n * Create from multiple field errors\n */\n static fromFields(errors: Record<string, string>): ValidationError {\n const issues = Object.entries(errors).map(([path, message]) => ({\n path,\n message,\n }));\n return new ValidationError(issues);\n }\n\n /**\n * Check if a specific field has errors\n */\n hasErrorFor(path: string): boolean {\n return this.issues.some((issue) => issue.path === path);\n }\n\n /**\n * Get errors for a specific field\n */\n getErrorsFor(path: string): ValidationIssue[] {\n return this.issues.filter((issue) => issue.path === path);\n }\n\n /**\n * Get first error message for a field\n */\n getFirstError(path: string): string | undefined {\n return this.issues.find((issue) => issue.path === path)?.message;\n }\n\n /**\n * Convert to flat error object\n */\n toFlatObject(): Record<string, string> {\n const result: Record<string, string> = {};\n for (const issue of this.issues) {\n if (!result[issue.path]) {\n result[issue.path] = issue.message;\n }\n }\n return result;\n }\n\n override toJSON(): Record<string, unknown> {\n return {\n error: this.name,\n message: this.message,\n code: this.code,\n status: this.status,\n // Strip `received` to prevent leaking sensitive input values (passwords, tokens)\n issues: this.issues.map(({ path, message, rule, expected }) => ({\n path,\n message,\n ...(rule !== undefined && { rule }),\n ...(expected !== undefined && { expected }),\n })),\n };\n }\n}\n\n/**\n * Required field missing\n */\nexport class RequiredFieldError extends ValidationError {\n constructor(field: string) {\n super(\n [{ path: field, message: `${field} is required`, rule: 'required' }],\n `${field} is required`\n );\n }\n}\n\n/**\n * Field type mismatch\n */\nexport class TypeMismatchError extends ValidationError {\n constructor(field: string, expected: string, received: string) {\n super(\n [\n {\n path: field,\n message: `Expected ${expected}, received ${received}`,\n rule: 'type',\n expected,\n received,\n },\n ],\n `${field} must be of type ${expected}`\n );\n }\n}\n\n/**\n * Value out of range\n */\nexport class RangeValidationError extends ValidationError {\n constructor(field: string, min?: number, max?: number) {\n const parts: string[] = [];\n if (min !== undefined) parts.push(`at least ${min}`);\n if (max !== undefined) parts.push(`at most ${max}`);\n const message = `${field} must be ${parts.join(' and ')}`;\n\n super([{ path: field, message, rule: 'range', expected: { min, max } }], message);\n }\n}\n\n/**\n * String length violation\n */\nexport class LengthError extends ValidationError {\n constructor(field: string, min?: number, max?: number) {\n const parts: string[] = [];\n if (min !== undefined) parts.push(`at least ${min} characters`);\n if (max !== undefined) parts.push(`at most ${max} characters`);\n const message = `${field} must be ${parts.join(' and ')}`;\n\n super([{ path: field, message, rule: 'length', expected: { min, max } }], message);\n }\n}\n\n/**\n * Pattern mismatch\n */\nexport class PatternError extends ValidationError {\n constructor(field: string, pattern: string, message?: string) {\n super(\n [\n {\n path: field,\n message: message ?? `${field} does not match required pattern`,\n rule: 'pattern',\n expected: pattern,\n },\n ],\n message ?? `${field} does not match required pattern`\n );\n }\n}\n\n/**\n * Invalid email format\n */\nexport class InvalidEmailError extends ValidationError {\n constructor(field = 'email') {\n super(\n [{ path: field, message: 'Invalid email address', rule: 'email' }],\n 'Invalid email address'\n );\n }\n}\n\n/**\n * Invalid URL format\n */\nexport class InvalidUrlError extends ValidationError {\n constructor(field = 'url') {\n super([{ path: field, message: 'Invalid URL', rule: 'url' }], 'Invalid URL');\n }\n}\n","/**\n * @nextrush/errors - Error Factory\n *\n * Factory functions for creating errors.\n *\n * @packageDocumentation\n */\n\nimport { HttpError, NextRushError } from './base';\nimport {\n BadGatewayError,\n BadRequestError,\n ConflictError,\n ForbiddenError,\n GatewayTimeoutError,\n InternalServerError,\n MethodNotAllowedError,\n NotFoundError,\n NotImplementedError,\n ServiceUnavailableError,\n TooManyRequestsError,\n UnauthorizedError,\n UnprocessableEntityError,\n type HttpErrorOptions,\n} from './http-errors';\n\n/**\n * HTTP status code to error class mapping\n */\nconst ERROR_MAP: Record<number, new (message?: string, options?: HttpErrorOptions) => HttpError> = {\n 400: BadRequestError,\n 401: UnauthorizedError,\n 403: ForbiddenError,\n 404: NotFoundError,\n 409: ConflictError,\n 422: UnprocessableEntityError,\n 429: TooManyRequestsError,\n 500: InternalServerError,\n 501: NotImplementedError,\n 502: BadGatewayError,\n 503: ServiceUnavailableError,\n 504: GatewayTimeoutError,\n};\n\n/**\n * Create an HTTP error by status code\n */\nexport function createError(\n status: number,\n message?: string,\n options?: HttpErrorOptions\n): HttpError {\n // Special case: MethodNotAllowedError requires allowedMethods array\n if (status === 405) {\n return new MethodNotAllowedError([], message, options);\n }\n\n const ErrorClass = ERROR_MAP[status];\n\n if (ErrorClass) {\n return new ErrorClass(message, options);\n }\n\n // Fallback: let HttpError constructor resolve the message via getHttpStatusMessage()\n return new HttpError(status, message, options);\n}\n\n/**\n * Create a 400 Bad Request error\n */\nexport function badRequest(message?: string, options?: HttpErrorOptions): BadRequestError {\n return new BadRequestError(message, options);\n}\n\n/**\n * Create a 401 Unauthorized error\n */\nexport function unauthorized(message?: string, options?: HttpErrorOptions): UnauthorizedError {\n return new UnauthorizedError(message, options);\n}\n\n/**\n * Create a 403 Forbidden error\n */\nexport function forbidden(message?: string, options?: HttpErrorOptions): ForbiddenError {\n return new ForbiddenError(message, options);\n}\n\n/**\n * Create a 404 Not Found error\n */\nexport function notFound(message?: string, options?: HttpErrorOptions): NotFoundError {\n return new NotFoundError(message, options);\n}\n\n/**\n * Create a 405 Method Not Allowed error\n */\nexport function methodNotAllowed(\n allowedMethods?: string[],\n message?: string,\n options?: HttpErrorOptions\n): MethodNotAllowedError {\n return new MethodNotAllowedError(allowedMethods, message, options);\n}\n\n/**\n * Create a 409 Conflict error\n */\nexport function conflict(message?: string, options?: HttpErrorOptions): ConflictError {\n return new ConflictError(message, options);\n}\n\n/**\n * Create a 422 Unprocessable Entity error\n */\nexport function unprocessableEntity(\n message?: string,\n options?: HttpErrorOptions\n): UnprocessableEntityError {\n return new UnprocessableEntityError(message, options);\n}\n\n/**\n * Create a 429 Too Many Requests error\n */\nexport function tooManyRequests(\n message?: string,\n options?: HttpErrorOptions & { retryAfter?: number }\n): TooManyRequestsError {\n return new TooManyRequestsError(message, options);\n}\n\n/**\n * Create a 500 Internal Server Error\n */\nexport function internalError(message?: string, options?: HttpErrorOptions): InternalServerError {\n return new InternalServerError(message, options);\n}\n\n/**\n * Create a 502 Bad Gateway error\n */\nexport function badGateway(message?: string, options?: HttpErrorOptions): BadGatewayError {\n return new BadGatewayError(message, options);\n}\n\n/**\n * Create a 503 Service Unavailable error\n */\nexport function serviceUnavailable(\n message?: string,\n options?: HttpErrorOptions & { retryAfter?: number }\n): ServiceUnavailableError {\n return new ServiceUnavailableError(message, options);\n}\n\n/**\n * Create a 504 Gateway Timeout error\n */\nexport function gatewayTimeout(message?: string, options?: HttpErrorOptions): GatewayTimeoutError {\n return new GatewayTimeoutError(message, options);\n}\n\n/**\n * Check if an error is a NextRush HttpError\n */\nexport function isHttpError(error: unknown): error is HttpError {\n return error instanceof HttpError;\n}\n\n/**\n * Get HTTP status from any error\n *\n * Checks NextRushError first (covers HttpError, ValidationError, and all subclasses),\n * then falls back to duck-typed status property.\n */\nexport function getErrorStatus(error: unknown): number {\n if (error instanceof NextRushError) {\n return error.status;\n }\n if (\n error != null &&\n typeof error === 'object' &&\n 'status' in error &&\n typeof (error as { status: unknown }).status === 'number'\n ) {\n const status = (error as { status: number }).status;\n return status >= 400 && status < 600 ? status : 500;\n }\n return 500;\n}\n\n/**\n * Get error message safe for client response\n *\n * Exposes message for any NextRushError (including ValidationError)\n * when the error's expose flag is true.\n */\nexport function getSafeErrorMessage(error: unknown): string {\n if (error instanceof NextRushError && error.expose) {\n return error.message;\n }\n return 'Internal Server Error';\n}\n","/**\n * @nextrush/errors - Error Handler Middleware\n *\n * Middleware for handling errors in NextRush applications.\n *\n * @packageDocumentation\n */\n\nimport type { Context, Middleware, Next } from '@nextrush/types';\nimport { HttpError, NextRushError, getHttpStatusMessage } from './base';\n\n/**\n * Minimal context interface for error handling.\n *\n * @deprecated Use `Context` from `@nextrush/types` instead.\n * Kept for backward compatibility — will be removed in v4.\n */\nexport interface ErrorContext {\n method: string;\n path: string;\n status: number;\n json: (data: unknown) => void;\n}\n\n/**\n * Error handler middleware function type.\n *\n * @deprecated Use `Middleware` from `@nextrush/types` instead.\n * Kept for backward compatibility — will be removed in v4.\n */\nexport type ErrorMiddleware = Middleware;\n\n/**\n * Error handler options\n */\nexport interface ErrorHandlerOptions {\n /** Include stack trace in development */\n includeStack?: boolean;\n\n /** Custom error logger */\n logger?: (error: Error, ctx: Context) => void;\n\n /** Custom error transformer */\n transform?: (error: Error, ctx: Context) => Record<string, unknown>;\n\n /** Handle specific error types */\n handlers?: Map<new (...args: unknown[]) => Error, (error: Error, ctx: Context) => void>;\n}\n\n/**\n * Default error logger\n */\nfunction defaultLogger(error: Error, ctx: Context): void {\n const status = error instanceof HttpError ? error.status : 500;\n\n if (status >= 500) {\n console.error(`[ERROR] ${ctx.method} ${ctx.path}:`, error);\n } else {\n console.warn(`[WARN] ${ctx.method} ${ctx.path}: ${error.message}`);\n }\n}\n\n/**\n * Create error handler middleware\n *\n * @example\n * ```typescript\n * const app = createApp();\n *\n * // Add error handler first\n * app.use(errorHandler({\n * includeStack: process.env.NODE_ENV !== 'production',\n * logger: (err, ctx) => myLogger.error(err),\n * }));\n * ```\n */\nexport function errorHandler(options: ErrorHandlerOptions = {}): Middleware {\n const { includeStack = false, logger = defaultLogger, transform, handlers } = options;\n\n return async (ctx: Context, next: Next): Promise<void> => {\n try {\n await next();\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n // Log the error\n logger(err, ctx);\n\n // Check for custom handlers\n if (handlers) {\n for (const [ErrorType, handler] of handlers) {\n if (err instanceof ErrorType) {\n handler(err, ctx);\n return;\n }\n }\n }\n\n // Determine status code\n let status = 500;\n let expose = false;\n let code = 'INTERNAL_ERROR';\n let details: Record<string, unknown> | undefined;\n\n if (err instanceof HttpError || err instanceof NextRushError) {\n status = err.status;\n expose = err.expose;\n code = err.code;\n details = err.details;\n }\n\n ctx.status = status;\n\n // Build response body\n let body: Record<string, unknown>;\n\n if (transform) {\n body = transform(err, ctx);\n } else {\n body = {\n error: expose ? err.name : getHttpStatusMessage(status),\n message: expose ? err.message : 'Internal Server Error',\n code,\n status,\n };\n\n if (expose && details) {\n body.details = details;\n }\n\n if (includeStack && err.stack) {\n body.stack = err.stack.split('\\n').map((line) => line.trim());\n }\n }\n\n ctx.json(body);\n }\n };\n}\n\n/**\n * Not found handler middleware - catches unhandled requests\n *\n * @example\n * ```typescript\n * // Add at the end of middleware chain\n * app.use(notFoundHandler());\n * ```\n */\nexport function notFoundHandler(message = 'Not Found'): Middleware {\n return async (ctx: Context, next: Next): Promise<void> => {\n await next();\n // Only handle if no response was sent and status indicates unhandled\n if (!ctx.responded && ctx.status === 404) {\n ctx.json({\n error: 'NotFoundError',\n message,\n code: 'NOT_FOUND',\n status: 404,\n });\n }\n };\n}\n\n/**\n * Catch async errors wrapper for route handlers\n *\n * @example\n * ```typescript\n * router.get('/users/:id', catchAsync(async (ctx) => {\n * const user = await db.users.findById(ctx.params.id);\n * if (!user) throw new NotFoundError('User not found');\n * ctx.json(user);\n * }));\n * ```\n *\n * @deprecated This wrapper is redundant — async errors propagate naturally\n * through the middleware chain and are caught by `errorHandler()`. Use the\n * handler directly instead.\n */\nexport function catchAsync(handler: (ctx: Context, next: Next) => Promise<void>): Middleware {\n return handler;\n}\n"],"mappings":"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","names":["V8Error","Error","NextRushError","status","code","expose","details","cause","message","options","name","captureStackTrace","toJSON","json","error","toResponse","body","HTTP_STATUS_MESSAGES","getHttpStatusMessage","HttpError","BadRequestError","HttpError","message","options","code","UnauthorizedError","PaymentRequiredError","ForbiddenError","NotFoundError","MethodNotAllowedError","allowedMethods","details","NotAcceptableError","ProxyAuthRequiredError","RequestTimeoutError","ConflictError","GoneError","LengthRequiredError","PreconditionFailedError","PayloadTooLargeError","UriTooLongError","UnsupportedMediaTypeError","RangeNotSatisfiableError","ExpectationFailedError","ImATeapotError","UnprocessableEntityError","LockedError","FailedDependencyError","TooEarlyError","UpgradeRequiredError","PreconditionRequiredError","TooManyRequestsError","retryAfter","undefined","RequestHeaderFieldsTooLargeError","UnavailableForLegalReasonsError","InternalServerError","expose","NotImplementedError","BadGatewayError","ServiceUnavailableError","GatewayTimeoutError","HttpVersionNotSupportedError","VariantAlsoNegotiatesError","InsufficientStorageError","LoopDetectedError","NotExtendedError","NetworkAuthRequiredError","ValidationError","NextRushError","issues","message","status","code","expose","fromField","path","rule","fromFields","errors","Object","entries","map","hasErrorFor","some","issue","getErrorsFor","filter","getFirstError","find","toFlatObject","result","toJSON","error","name","expected","undefined","RequiredFieldError","field","TypeMismatchError","received","RangeValidationError","min","max","parts","push","join","LengthError","PatternError","pattern","InvalidEmailError","InvalidUrlError","ERROR_MAP","BadRequestError","UnauthorizedError","ForbiddenError","NotFoundError","ConflictError","UnprocessableEntityError","TooManyRequestsError","InternalServerError","NotImplementedError","BadGatewayError","ServiceUnavailableError","GatewayTimeoutError","createError","status","message","options","MethodNotAllowedError","ErrorClass","HttpError","badRequest","unauthorized","forbidden","notFound","methodNotAllowed","allowedMethods","conflict","unprocessableEntity","tooManyRequests","internalError","badGateway","serviceUnavailable","gatewayTimeout","isHttpError","error","getErrorStatus","NextRushError","getSafeErrorMessage","expose","defaultLogger","error","ctx","status","HttpError","console","method","path","warn","message","errorHandler","options","includeStack","logger","transform","handlers","next","err","Error","String","ErrorType","handler","expose","code","details","NextRushError","body","name","getHttpStatusMessage","stack","split","map","line","trim","json","notFoundHandler","responded","catchAsync"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nextrush/errors",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Standardized error handling for NextRush",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@nextrush/types": "3.0.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsup": "^8.5.1",
|
|
24
|
+
"typescript": "^6.0.2",
|
|
25
|
+
"vitest": "^4.1.4"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"nextrush",
|
|
29
|
+
"errors",
|
|
30
|
+
"http-errors",
|
|
31
|
+
"middleware"
|
|
32
|
+
],
|
|
33
|
+
"author": {
|
|
34
|
+
"name": "Tanzim Hossain",
|
|
35
|
+
"email": "tanzimhossain2@gmail.com",
|
|
36
|
+
"url": "https://github.com/0xTanzim"
|
|
37
|
+
},
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/0xTanzim/nextrush.git",
|
|
42
|
+
"directory": "packages/errors"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=22.0.0"
|
|
49
|
+
},
|
|
50
|
+
"sideEffects": false,
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "tsup",
|
|
53
|
+
"dev": "tsup --watch",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:watch": "vitest",
|
|
56
|
+
"typecheck": "tsc --noEmit",
|
|
57
|
+
"clean": "rm -rf dist"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nextrush/errors - Base Error Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import { HttpError, NextRushError } from '../base';
|
|
7
|
+
|
|
8
|
+
describe('NextRushError', () => {
|
|
9
|
+
describe('constructor', () => {
|
|
10
|
+
it('should create error with message', () => {
|
|
11
|
+
const error = new NextRushError('Something went wrong');
|
|
12
|
+
expect(error.message).toBe('Something went wrong');
|
|
13
|
+
expect(error.name).toBe('NextRushError');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should set default values', () => {
|
|
17
|
+
const error = new NextRushError('Test');
|
|
18
|
+
expect(error.status).toBe(500);
|
|
19
|
+
expect(error.code).toBe('INTERNAL_ERROR');
|
|
20
|
+
expect(error.expose).toBe(false);
|
|
21
|
+
expect(error.details).toBeUndefined();
|
|
22
|
+
expect(error.cause).toBeUndefined();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should accept custom status', () => {
|
|
26
|
+
const error = new NextRushError('Bad request', { status: 400 });
|
|
27
|
+
expect(error.status).toBe(400);
|
|
28
|
+
expect(error.expose).toBe(true); // 4xx errors are exposed by default
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should accept custom code', () => {
|
|
32
|
+
const error = new NextRushError('Custom error', { code: 'CUSTOM_CODE' });
|
|
33
|
+
expect(error.code).toBe('CUSTOM_CODE');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should accept custom expose flag', () => {
|
|
37
|
+
const error = new NextRushError('Server error', { status: 500, expose: true });
|
|
38
|
+
expect(error.expose).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should accept details', () => {
|
|
42
|
+
const details = { field: 'email', constraint: 'unique' };
|
|
43
|
+
const error = new NextRushError('Validation failed', { details });
|
|
44
|
+
expect(error.details).toEqual(details);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should accept cause', () => {
|
|
48
|
+
const cause = new Error('Original error');
|
|
49
|
+
const error = new NextRushError('Wrapped error', { cause });
|
|
50
|
+
expect(error.cause).toBe(cause);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should set expose true for 4xx errors by default', () => {
|
|
54
|
+
const error400 = new NextRushError('Bad request', { status: 400 });
|
|
55
|
+
const error404 = new NextRushError('Not found', { status: 404 });
|
|
56
|
+
const error499 = new NextRushError('Client error', { status: 499 });
|
|
57
|
+
|
|
58
|
+
expect(error400.expose).toBe(true);
|
|
59
|
+
expect(error404.expose).toBe(true);
|
|
60
|
+
expect(error499.expose).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should set expose false for 5xx errors by default', () => {
|
|
64
|
+
const error500 = new NextRushError('Server error', { status: 500 });
|
|
65
|
+
const error503 = new NextRushError('Unavailable', { status: 503 });
|
|
66
|
+
|
|
67
|
+
expect(error500.expose).toBe(false);
|
|
68
|
+
expect(error503.expose).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should be instanceof Error', () => {
|
|
72
|
+
const error = new NextRushError('Test');
|
|
73
|
+
expect(error).toBeInstanceOf(Error);
|
|
74
|
+
expect(error).toBeInstanceOf(NextRushError);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should have proper stack trace', () => {
|
|
78
|
+
const error = new NextRushError('Test');
|
|
79
|
+
expect(error.stack).toBeDefined();
|
|
80
|
+
expect(error.stack).toContain('NextRushError');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('toJSON', () => {
|
|
85
|
+
it('should return JSON representation', () => {
|
|
86
|
+
const error = new NextRushError('Bad request', { status: 400, code: 'BAD_REQUEST' });
|
|
87
|
+
const json = error.toJSON();
|
|
88
|
+
|
|
89
|
+
expect(json).toEqual({
|
|
90
|
+
error: 'NextRushError',
|
|
91
|
+
message: 'Bad request',
|
|
92
|
+
code: 'BAD_REQUEST',
|
|
93
|
+
status: 400,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should hide message for non-exposed errors', () => {
|
|
98
|
+
const error = new NextRushError('Secret error', { status: 500, expose: false });
|
|
99
|
+
const json = error.toJSON();
|
|
100
|
+
|
|
101
|
+
expect(json.message).toBe('Internal Server Error');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should include details when exposed', () => {
|
|
105
|
+
const error = new NextRushError('Error', {
|
|
106
|
+
status: 400,
|
|
107
|
+
expose: true,
|
|
108
|
+
details: { field: 'email' },
|
|
109
|
+
});
|
|
110
|
+
const json = error.toJSON();
|
|
111
|
+
|
|
112
|
+
expect(json.details).toEqual({ field: 'email' });
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should not include details when not exposed', () => {
|
|
116
|
+
const error = new NextRushError('Error', {
|
|
117
|
+
status: 500,
|
|
118
|
+
expose: false,
|
|
119
|
+
details: { sensitive: 'data' },
|
|
120
|
+
});
|
|
121
|
+
const json = error.toJSON();
|
|
122
|
+
|
|
123
|
+
expect(json.details).toBeUndefined();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('toResponse', () => {
|
|
128
|
+
it('should return response object', () => {
|
|
129
|
+
const error = new NextRushError('Not found', { status: 404, code: 'NOT_FOUND' });
|
|
130
|
+
const response = error.toResponse();
|
|
131
|
+
|
|
132
|
+
expect(response.status).toBe(404);
|
|
133
|
+
expect(response.body.message).toBe('Not found');
|
|
134
|
+
expect(response.body.code).toBe('NOT_FOUND');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('HttpError', () => {
|
|
140
|
+
describe('constructor', () => {
|
|
141
|
+
it('should create error with status and message', () => {
|
|
142
|
+
const error = new HttpError(404, 'Resource not found');
|
|
143
|
+
expect(error.status).toBe(404);
|
|
144
|
+
expect(error.message).toBe('Resource not found');
|
|
145
|
+
expect(error.name).toBe('HttpError');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should set default code based on status', () => {
|
|
149
|
+
const error = new HttpError(404, 'Not found');
|
|
150
|
+
expect(error.code).toBe('HTTP_404');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should accept custom code', () => {
|
|
154
|
+
const error = new HttpError(404, 'Not found', { code: 'RESOURCE_NOT_FOUND' });
|
|
155
|
+
expect(error.code).toBe('RESOURCE_NOT_FOUND');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should accept options', () => {
|
|
159
|
+
const cause = new Error('Original');
|
|
160
|
+
const error = new HttpError(400, 'Bad request', {
|
|
161
|
+
code: 'VALIDATION_FAILED',
|
|
162
|
+
expose: true,
|
|
163
|
+
details: { field: 'name' },
|
|
164
|
+
cause,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
expect(error.code).toBe('VALIDATION_FAILED');
|
|
168
|
+
expect(error.expose).toBe(true);
|
|
169
|
+
expect(error.details).toEqual({ field: 'name' });
|
|
170
|
+
expect(error.cause).toBe(cause);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should be instanceof NextRushError', () => {
|
|
174
|
+
const error = new HttpError(500, 'Error');
|
|
175
|
+
expect(error).toBeInstanceOf(Error);
|
|
176
|
+
expect(error).toBeInstanceOf(NextRushError);
|
|
177
|
+
expect(error).toBeInstanceOf(HttpError);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nextrush/errors - Factory Function Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from 'vitest';
|
|
6
|
+
import { HttpError } from '../base';
|
|
7
|
+
import {
|
|
8
|
+
badGateway,
|
|
9
|
+
badRequest,
|
|
10
|
+
conflict,
|
|
11
|
+
createError,
|
|
12
|
+
forbidden,
|
|
13
|
+
gatewayTimeout,
|
|
14
|
+
getErrorStatus,
|
|
15
|
+
getSafeErrorMessage,
|
|
16
|
+
internalError,
|
|
17
|
+
isHttpError,
|
|
18
|
+
methodNotAllowed,
|
|
19
|
+
notFound,
|
|
20
|
+
serviceUnavailable,
|
|
21
|
+
tooManyRequests,
|
|
22
|
+
unauthorized,
|
|
23
|
+
unprocessableEntity,
|
|
24
|
+
} from '../factory';
|
|
25
|
+
import {
|
|
26
|
+
BadGatewayError,
|
|
27
|
+
BadRequestError,
|
|
28
|
+
ConflictError,
|
|
29
|
+
ForbiddenError,
|
|
30
|
+
GatewayTimeoutError,
|
|
31
|
+
InternalServerError,
|
|
32
|
+
MethodNotAllowedError,
|
|
33
|
+
NotFoundError,
|
|
34
|
+
ServiceUnavailableError,
|
|
35
|
+
TooManyRequestsError,
|
|
36
|
+
UnauthorizedError,
|
|
37
|
+
UnprocessableEntityError,
|
|
38
|
+
} from '../http-errors';
|
|
39
|
+
|
|
40
|
+
describe('createError', () => {
|
|
41
|
+
it('should create BadRequestError for 400', () => {
|
|
42
|
+
const error = createError(400);
|
|
43
|
+
expect(error).toBeInstanceOf(BadRequestError);
|
|
44
|
+
expect(error.status).toBe(400);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should create UnauthorizedError for 401', () => {
|
|
48
|
+
const error = createError(401);
|
|
49
|
+
expect(error).toBeInstanceOf(UnauthorizedError);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should create ForbiddenError for 403', () => {
|
|
53
|
+
const error = createError(403);
|
|
54
|
+
expect(error).toBeInstanceOf(ForbiddenError);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should create NotFoundError for 404', () => {
|
|
58
|
+
const error = createError(404, 'Resource not found');
|
|
59
|
+
expect(error).toBeInstanceOf(NotFoundError);
|
|
60
|
+
expect(error.message).toBe('Resource not found');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should create ConflictError for 409', () => {
|
|
64
|
+
const error = createError(409);
|
|
65
|
+
expect(error).toBeInstanceOf(ConflictError);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should create UnprocessableEntityError for 422', () => {
|
|
69
|
+
const error = createError(422);
|
|
70
|
+
expect(error).toBeInstanceOf(UnprocessableEntityError);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should create InternalServerError for 500', () => {
|
|
74
|
+
const error = createError(500);
|
|
75
|
+
expect(error).toBeInstanceOf(InternalServerError);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should create BadGatewayError for 502', () => {
|
|
79
|
+
const error = createError(502);
|
|
80
|
+
expect(error).toBeInstanceOf(BadGatewayError);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should create GatewayTimeoutError for 504', () => {
|
|
84
|
+
const error = createError(504);
|
|
85
|
+
expect(error).toBeInstanceOf(GatewayTimeoutError);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should create generic HttpError for unmapped status', () => {
|
|
89
|
+
const error = createError(418);
|
|
90
|
+
expect(error).toBeInstanceOf(HttpError);
|
|
91
|
+
expect(error.status).toBe(418);
|
|
92
|
+
expect(error.message).toBe("I'm a Teapot");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should pass options to error', () => {
|
|
96
|
+
const error = createError(400, 'Custom message', { details: { field: 'name' } });
|
|
97
|
+
expect(error.message).toBe('Custom message');
|
|
98
|
+
expect(error.details).toEqual({ field: 'name' });
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('Factory functions', () => {
|
|
103
|
+
describe('badRequest', () => {
|
|
104
|
+
it('should create BadRequestError', () => {
|
|
105
|
+
const error = badRequest();
|
|
106
|
+
expect(error).toBeInstanceOf(BadRequestError);
|
|
107
|
+
expect(error.status).toBe(400);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should accept message', () => {
|
|
111
|
+
const error = badRequest('Invalid input');
|
|
112
|
+
expect(error.message).toBe('Invalid input');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should accept options', () => {
|
|
116
|
+
const error = badRequest('Error', { details: { field: 'email' } });
|
|
117
|
+
expect(error.details).toEqual({ field: 'email' });
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('unauthorized', () => {
|
|
122
|
+
it('should create UnauthorizedError', () => {
|
|
123
|
+
const error = unauthorized();
|
|
124
|
+
expect(error).toBeInstanceOf(UnauthorizedError);
|
|
125
|
+
expect(error.status).toBe(401);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should accept message', () => {
|
|
129
|
+
const error = unauthorized('Token expired');
|
|
130
|
+
expect(error.message).toBe('Token expired');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('forbidden', () => {
|
|
135
|
+
it('should create ForbiddenError', () => {
|
|
136
|
+
const error = forbidden();
|
|
137
|
+
expect(error).toBeInstanceOf(ForbiddenError);
|
|
138
|
+
expect(error.status).toBe(403);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should accept message', () => {
|
|
142
|
+
const error = forbidden('Access denied');
|
|
143
|
+
expect(error.message).toBe('Access denied');
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('notFound', () => {
|
|
148
|
+
it('should create NotFoundError', () => {
|
|
149
|
+
const error = notFound();
|
|
150
|
+
expect(error).toBeInstanceOf(NotFoundError);
|
|
151
|
+
expect(error.status).toBe(404);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should accept message', () => {
|
|
155
|
+
const error = notFound('User not found');
|
|
156
|
+
expect(error.message).toBe('User not found');
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('methodNotAllowed', () => {
|
|
161
|
+
it('should create MethodNotAllowedError', () => {
|
|
162
|
+
const error = methodNotAllowed(['GET', 'POST']);
|
|
163
|
+
expect(error).toBeInstanceOf(MethodNotAllowedError);
|
|
164
|
+
expect(error.status).toBe(405);
|
|
165
|
+
expect(error.allowedMethods).toEqual(['GET', 'POST']);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should accept message', () => {
|
|
169
|
+
const error = methodNotAllowed(['GET'], 'Only GET allowed');
|
|
170
|
+
expect(error.message).toBe('Only GET allowed');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('conflict', () => {
|
|
175
|
+
it('should create ConflictError', () => {
|
|
176
|
+
const error = conflict();
|
|
177
|
+
expect(error).toBeInstanceOf(ConflictError);
|
|
178
|
+
expect(error.status).toBe(409);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should accept message', () => {
|
|
182
|
+
const error = conflict('Resource already exists');
|
|
183
|
+
expect(error.message).toBe('Resource already exists');
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('unprocessableEntity', () => {
|
|
188
|
+
it('should create UnprocessableEntityError', () => {
|
|
189
|
+
const error = unprocessableEntity();
|
|
190
|
+
expect(error).toBeInstanceOf(UnprocessableEntityError);
|
|
191
|
+
expect(error.status).toBe(422);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('tooManyRequests', () => {
|
|
196
|
+
it('should create TooManyRequestsError', () => {
|
|
197
|
+
const error = tooManyRequests();
|
|
198
|
+
expect(error).toBeInstanceOf(TooManyRequestsError);
|
|
199
|
+
expect(error.status).toBe(429);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should accept retryAfter', () => {
|
|
203
|
+
const error = tooManyRequests('Rate limited', { retryAfter: 60 });
|
|
204
|
+
expect(error.retryAfter).toBe(60);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('internalError', () => {
|
|
209
|
+
it('should create InternalServerError', () => {
|
|
210
|
+
const error = internalError();
|
|
211
|
+
expect(error).toBeInstanceOf(InternalServerError);
|
|
212
|
+
expect(error.status).toBe(500);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should accept message', () => {
|
|
216
|
+
const error = internalError('Database error');
|
|
217
|
+
expect(error.message).toBe('Database error');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('badGateway', () => {
|
|
222
|
+
it('should create BadGatewayError', () => {
|
|
223
|
+
const error = badGateway();
|
|
224
|
+
expect(error).toBeInstanceOf(BadGatewayError);
|
|
225
|
+
expect(error.status).toBe(502);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('serviceUnavailable', () => {
|
|
230
|
+
it('should create ServiceUnavailableError', () => {
|
|
231
|
+
const error = serviceUnavailable();
|
|
232
|
+
expect(error).toBeInstanceOf(ServiceUnavailableError);
|
|
233
|
+
expect(error.status).toBe(503);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should accept retryAfter', () => {
|
|
237
|
+
const error = serviceUnavailable('Maintenance', { retryAfter: 3600 });
|
|
238
|
+
expect(error.retryAfter).toBe(3600);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
describe('gatewayTimeout', () => {
|
|
243
|
+
it('should create GatewayTimeoutError', () => {
|
|
244
|
+
const error = gatewayTimeout();
|
|
245
|
+
expect(error).toBeInstanceOf(GatewayTimeoutError);
|
|
246
|
+
expect(error.status).toBe(504);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('isHttpError', () => {
|
|
252
|
+
it('should return true for HttpError', () => {
|
|
253
|
+
expect(isHttpError(new HttpError(500, 'Error'))).toBe(true);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('should return true for subclasses', () => {
|
|
257
|
+
expect(isHttpError(new BadRequestError())).toBe(true);
|
|
258
|
+
expect(isHttpError(new NotFoundError())).toBe(true);
|
|
259
|
+
expect(isHttpError(new InternalServerError())).toBe(true);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should return false for regular Error', () => {
|
|
263
|
+
expect(isHttpError(new Error('Regular error'))).toBe(false);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should return false for non-errors', () => {
|
|
267
|
+
expect(isHttpError('string')).toBe(false);
|
|
268
|
+
expect(isHttpError(123)).toBe(false);
|
|
269
|
+
expect(isHttpError(null)).toBe(false);
|
|
270
|
+
expect(isHttpError(undefined)).toBe(false);
|
|
271
|
+
expect(isHttpError({})).toBe(false);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe('getErrorStatus', () => {
|
|
276
|
+
it('should return status from HttpError', () => {
|
|
277
|
+
expect(getErrorStatus(new BadRequestError())).toBe(400);
|
|
278
|
+
expect(getErrorStatus(new NotFoundError())).toBe(404);
|
|
279
|
+
expect(getErrorStatus(new InternalServerError())).toBe(500);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('should return 500 for regular Error', () => {
|
|
283
|
+
expect(getErrorStatus(new Error('Test'))).toBe(500);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should return 500 for non-errors', () => {
|
|
287
|
+
expect(getErrorStatus('string')).toBe(500);
|
|
288
|
+
expect(getErrorStatus(null)).toBe(500);
|
|
289
|
+
expect(getErrorStatus(undefined)).toBe(500);
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe('getSafeErrorMessage', () => {
|
|
294
|
+
it('should return message for exposed errors', () => {
|
|
295
|
+
expect(getSafeErrorMessage(new BadRequestError('Invalid input'))).toBe('Invalid input');
|
|
296
|
+
expect(getSafeErrorMessage(new NotFoundError('User not found'))).toBe('User not found');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('should return generic message for non-exposed errors', () => {
|
|
300
|
+
expect(getSafeErrorMessage(new InternalServerError('Database crashed'))).toBe(
|
|
301
|
+
'Internal Server Error'
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should return generic message for regular Error', () => {
|
|
306
|
+
expect(getSafeErrorMessage(new Error('Sensitive info'))).toBe('Internal Server Error');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should return generic message for non-errors', () => {
|
|
310
|
+
expect(getSafeErrorMessage('string error')).toBe('Internal Server Error');
|
|
311
|
+
});
|
|
312
|
+
});
|