@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,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nextrush/errors - HTTP Error Classes
|
|
3
|
+
*
|
|
4
|
+
* Standard HTTP error classes for common status codes.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { HttpError } from './base';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for creating HTTP errors
|
|
13
|
+
*/
|
|
14
|
+
export interface HttpErrorOptions {
|
|
15
|
+
code?: string;
|
|
16
|
+
expose?: boolean;
|
|
17
|
+
details?: Record<string, unknown>;
|
|
18
|
+
cause?: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// 4xx Client Errors
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 400 Bad Request - Invalid syntax or malformed request
|
|
27
|
+
*/
|
|
28
|
+
export class BadRequestError extends HttpError {
|
|
29
|
+
constructor(message = 'Bad Request', options: HttpErrorOptions = {}) {
|
|
30
|
+
super(400, message, { code: 'BAD_REQUEST', ...options });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 401 Unauthorized - Authentication required
|
|
36
|
+
*/
|
|
37
|
+
export class UnauthorizedError extends HttpError {
|
|
38
|
+
constructor(message = 'Unauthorized', options: HttpErrorOptions = {}) {
|
|
39
|
+
super(401, message, { code: 'UNAUTHORIZED', ...options });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 402 Payment Required - Payment needed
|
|
45
|
+
*/
|
|
46
|
+
export class PaymentRequiredError extends HttpError {
|
|
47
|
+
constructor(message = 'Payment Required', options: HttpErrorOptions = {}) {
|
|
48
|
+
super(402, message, { code: 'PAYMENT_REQUIRED', ...options });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 403 Forbidden - Access denied
|
|
54
|
+
*/
|
|
55
|
+
export class ForbiddenError extends HttpError {
|
|
56
|
+
constructor(message = 'Forbidden', options: HttpErrorOptions = {}) {
|
|
57
|
+
super(403, message, { code: 'FORBIDDEN', ...options });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 404 Not Found - Resource not found
|
|
63
|
+
*/
|
|
64
|
+
export class NotFoundError extends HttpError {
|
|
65
|
+
constructor(message = 'Not Found', options: HttpErrorOptions = {}) {
|
|
66
|
+
super(404, message, { code: 'NOT_FOUND', ...options });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 405 Method Not Allowed - HTTP method not supported
|
|
72
|
+
*/
|
|
73
|
+
export class MethodNotAllowedError extends HttpError {
|
|
74
|
+
readonly allowedMethods: string[];
|
|
75
|
+
|
|
76
|
+
constructor(
|
|
77
|
+
allowedMethods: string[] = [],
|
|
78
|
+
message = 'Method Not Allowed',
|
|
79
|
+
options: HttpErrorOptions = {}
|
|
80
|
+
) {
|
|
81
|
+
super(405, message, {
|
|
82
|
+
code: 'METHOD_NOT_ALLOWED',
|
|
83
|
+
details: { allowedMethods },
|
|
84
|
+
...options,
|
|
85
|
+
});
|
|
86
|
+
this.allowedMethods = allowedMethods;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 406 Not Acceptable - Cannot produce acceptable response
|
|
92
|
+
*/
|
|
93
|
+
export class NotAcceptableError extends HttpError {
|
|
94
|
+
constructor(message = 'Not Acceptable', options: HttpErrorOptions = {}) {
|
|
95
|
+
super(406, message, { code: 'NOT_ACCEPTABLE', ...options });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 407 Proxy Authentication Required
|
|
101
|
+
*/
|
|
102
|
+
export class ProxyAuthRequiredError extends HttpError {
|
|
103
|
+
constructor(message = 'Proxy Authentication Required', options: HttpErrorOptions = {}) {
|
|
104
|
+
super(407, message, { code: 'PROXY_AUTH_REQUIRED', ...options });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 408 Request Timeout - Client took too long
|
|
110
|
+
*/
|
|
111
|
+
export class RequestTimeoutError extends HttpError {
|
|
112
|
+
constructor(message = 'Request Timeout', options: HttpErrorOptions = {}) {
|
|
113
|
+
super(408, message, { code: 'REQUEST_TIMEOUT', ...options });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 409 Conflict - Request conflicts with current state
|
|
119
|
+
*/
|
|
120
|
+
export class ConflictError extends HttpError {
|
|
121
|
+
constructor(message = 'Conflict', options: HttpErrorOptions = {}) {
|
|
122
|
+
super(409, message, { code: 'CONFLICT', ...options });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 410 Gone - Resource permanently deleted
|
|
128
|
+
*/
|
|
129
|
+
export class GoneError extends HttpError {
|
|
130
|
+
constructor(message = 'Gone', options: HttpErrorOptions = {}) {
|
|
131
|
+
super(410, message, { code: 'GONE', ...options });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 411 Length Required - Content-Length header required
|
|
137
|
+
*/
|
|
138
|
+
export class LengthRequiredError extends HttpError {
|
|
139
|
+
constructor(message = 'Length Required', options: HttpErrorOptions = {}) {
|
|
140
|
+
super(411, message, { code: 'LENGTH_REQUIRED', ...options });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 412 Precondition Failed - Precondition in headers not met
|
|
146
|
+
*/
|
|
147
|
+
export class PreconditionFailedError extends HttpError {
|
|
148
|
+
constructor(message = 'Precondition Failed', options: HttpErrorOptions = {}) {
|
|
149
|
+
super(412, message, { code: 'PRECONDITION_FAILED', ...options });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 413 Payload Too Large - Request body too large
|
|
155
|
+
*/
|
|
156
|
+
export class PayloadTooLargeError extends HttpError {
|
|
157
|
+
constructor(message = 'Payload Too Large', options: HttpErrorOptions = {}) {
|
|
158
|
+
super(413, message, { code: 'PAYLOAD_TOO_LARGE', ...options });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 414 URI Too Long - Request URI too long
|
|
164
|
+
*/
|
|
165
|
+
export class UriTooLongError extends HttpError {
|
|
166
|
+
constructor(message = 'URI Too Long', options: HttpErrorOptions = {}) {
|
|
167
|
+
super(414, message, { code: 'URI_TOO_LONG', ...options });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 415 Unsupported Media Type - Content type not supported
|
|
173
|
+
*/
|
|
174
|
+
export class UnsupportedMediaTypeError extends HttpError {
|
|
175
|
+
constructor(message = 'Unsupported Media Type', options: HttpErrorOptions = {}) {
|
|
176
|
+
super(415, message, { code: 'UNSUPPORTED_MEDIA_TYPE', ...options });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 416 Range Not Satisfiable - Cannot satisfy Range header
|
|
182
|
+
*/
|
|
183
|
+
export class RangeNotSatisfiableError extends HttpError {
|
|
184
|
+
constructor(message = 'Range Not Satisfiable', options: HttpErrorOptions = {}) {
|
|
185
|
+
super(416, message, { code: 'RANGE_NOT_SATISFIABLE', ...options });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 417 Expectation Failed - Cannot meet Expect header
|
|
191
|
+
*/
|
|
192
|
+
export class ExpectationFailedError extends HttpError {
|
|
193
|
+
constructor(message = 'Expectation Failed', options: HttpErrorOptions = {}) {
|
|
194
|
+
super(417, message, { code: 'EXPECTATION_FAILED', ...options });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 418 I'm a Teapot - RFC 2324
|
|
200
|
+
*/
|
|
201
|
+
export class ImATeapotError extends HttpError {
|
|
202
|
+
constructor(message = "I'm a teapot", options: HttpErrorOptions = {}) {
|
|
203
|
+
super(418, message, { code: 'IM_A_TEAPOT', ...options });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 422 Unprocessable Entity - Semantic errors
|
|
209
|
+
*/
|
|
210
|
+
export class UnprocessableEntityError extends HttpError {
|
|
211
|
+
constructor(message = 'Unprocessable Entity', options: HttpErrorOptions = {}) {
|
|
212
|
+
super(422, message, { code: 'UNPROCESSABLE_ENTITY', ...options });
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* 423 Locked - Resource is locked
|
|
218
|
+
*/
|
|
219
|
+
export class LockedError extends HttpError {
|
|
220
|
+
constructor(message = 'Locked', options: HttpErrorOptions = {}) {
|
|
221
|
+
super(423, message, { code: 'LOCKED', ...options });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 424 Failed Dependency - Dependent request failed
|
|
227
|
+
*/
|
|
228
|
+
export class FailedDependencyError extends HttpError {
|
|
229
|
+
constructor(message = 'Failed Dependency', options: HttpErrorOptions = {}) {
|
|
230
|
+
super(424, message, { code: 'FAILED_DEPENDENCY', ...options });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 425 Too Early - Request replayed too early
|
|
236
|
+
*/
|
|
237
|
+
export class TooEarlyError extends HttpError {
|
|
238
|
+
constructor(message = 'Too Early', options: HttpErrorOptions = {}) {
|
|
239
|
+
super(425, message, { code: 'TOO_EARLY', ...options });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* 426 Upgrade Required - Client should upgrade protocol
|
|
245
|
+
*/
|
|
246
|
+
export class UpgradeRequiredError extends HttpError {
|
|
247
|
+
constructor(message = 'Upgrade Required', options: HttpErrorOptions = {}) {
|
|
248
|
+
super(426, message, { code: 'UPGRADE_REQUIRED', ...options });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 428 Precondition Required - Origin server requires conditional request
|
|
254
|
+
*/
|
|
255
|
+
export class PreconditionRequiredError extends HttpError {
|
|
256
|
+
constructor(message = 'Precondition Required', options: HttpErrorOptions = {}) {
|
|
257
|
+
super(428, message, { code: 'PRECONDITION_REQUIRED', ...options });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* 429 Too Many Requests - Rate limit exceeded
|
|
263
|
+
*/
|
|
264
|
+
export class TooManyRequestsError extends HttpError {
|
|
265
|
+
readonly retryAfter?: number;
|
|
266
|
+
|
|
267
|
+
constructor(
|
|
268
|
+
message = 'Too Many Requests',
|
|
269
|
+
options: HttpErrorOptions & { retryAfter?: number } = {}
|
|
270
|
+
) {
|
|
271
|
+
super(429, message, {
|
|
272
|
+
code: 'TOO_MANY_REQUESTS',
|
|
273
|
+
details: options.retryAfter ? { retryAfter: options.retryAfter } : undefined,
|
|
274
|
+
...options,
|
|
275
|
+
});
|
|
276
|
+
this.retryAfter = options.retryAfter;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 431 Request Header Fields Too Large
|
|
282
|
+
*/
|
|
283
|
+
export class RequestHeaderFieldsTooLargeError extends HttpError {
|
|
284
|
+
constructor(message = 'Request Header Fields Too Large', options: HttpErrorOptions = {}) {
|
|
285
|
+
super(431, message, { code: 'REQUEST_HEADER_FIELDS_TOO_LARGE', ...options });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 451 Unavailable For Legal Reasons
|
|
291
|
+
*/
|
|
292
|
+
export class UnavailableForLegalReasonsError extends HttpError {
|
|
293
|
+
constructor(message = 'Unavailable For Legal Reasons', options: HttpErrorOptions = {}) {
|
|
294
|
+
super(451, message, { code: 'UNAVAILABLE_FOR_LEGAL_REASONS', ...options });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// =============================================================================
|
|
299
|
+
// 5xx Server Errors
|
|
300
|
+
// =============================================================================
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* 500 Internal Server Error - Generic server error
|
|
304
|
+
*/
|
|
305
|
+
export class InternalServerError extends HttpError {
|
|
306
|
+
constructor(message = 'Internal Server Error', options: HttpErrorOptions = {}) {
|
|
307
|
+
super(500, message, { code: 'INTERNAL_SERVER_ERROR', expose: false, ...options });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* 501 Not Implemented - Feature not implemented
|
|
313
|
+
*/
|
|
314
|
+
export class NotImplementedError extends HttpError {
|
|
315
|
+
constructor(message = 'Not Implemented', options: HttpErrorOptions = {}) {
|
|
316
|
+
super(501, message, { code: 'NOT_IMPLEMENTED', expose: false, ...options });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 502 Bad Gateway - Invalid upstream response
|
|
322
|
+
*/
|
|
323
|
+
export class BadGatewayError extends HttpError {
|
|
324
|
+
constructor(message = 'Bad Gateway', options: HttpErrorOptions = {}) {
|
|
325
|
+
super(502, message, { code: 'BAD_GATEWAY', expose: false, ...options });
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 503 Service Unavailable - Server temporarily unavailable
|
|
331
|
+
*/
|
|
332
|
+
export class ServiceUnavailableError extends HttpError {
|
|
333
|
+
readonly retryAfter?: number;
|
|
334
|
+
|
|
335
|
+
constructor(
|
|
336
|
+
message = 'Service Unavailable',
|
|
337
|
+
options: HttpErrorOptions & { retryAfter?: number } = {}
|
|
338
|
+
) {
|
|
339
|
+
super(503, message, {
|
|
340
|
+
code: 'SERVICE_UNAVAILABLE',
|
|
341
|
+
expose: false,
|
|
342
|
+
...options,
|
|
343
|
+
});
|
|
344
|
+
this.retryAfter = options.retryAfter;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* 504 Gateway Timeout - Upstream timeout
|
|
350
|
+
*/
|
|
351
|
+
export class GatewayTimeoutError extends HttpError {
|
|
352
|
+
constructor(message = 'Gateway Timeout', options: HttpErrorOptions = {}) {
|
|
353
|
+
super(504, message, { code: 'GATEWAY_TIMEOUT', expose: false, ...options });
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* 505 HTTP Version Not Supported
|
|
359
|
+
*/
|
|
360
|
+
export class HttpVersionNotSupportedError extends HttpError {
|
|
361
|
+
constructor(message = 'HTTP Version Not Supported', options: HttpErrorOptions = {}) {
|
|
362
|
+
super(505, message, { code: 'HTTP_VERSION_NOT_SUPPORTED', expose: false, ...options });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* 506 Variant Also Negotiates
|
|
368
|
+
*/
|
|
369
|
+
export class VariantAlsoNegotiatesError extends HttpError {
|
|
370
|
+
constructor(message = 'Variant Also Negotiates', options: HttpErrorOptions = {}) {
|
|
371
|
+
super(506, message, { code: 'VARIANT_ALSO_NEGOTIATES', expose: false, ...options });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 507 Insufficient Storage
|
|
377
|
+
*/
|
|
378
|
+
export class InsufficientStorageError extends HttpError {
|
|
379
|
+
constructor(message = 'Insufficient Storage', options: HttpErrorOptions = {}) {
|
|
380
|
+
super(507, message, { code: 'INSUFFICIENT_STORAGE', expose: false, ...options });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* 508 Loop Detected
|
|
386
|
+
*/
|
|
387
|
+
export class LoopDetectedError extends HttpError {
|
|
388
|
+
constructor(message = 'Loop Detected', options: HttpErrorOptions = {}) {
|
|
389
|
+
super(508, message, { code: 'LOOP_DETECTED', expose: false, ...options });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* 510 Not Extended
|
|
395
|
+
*/
|
|
396
|
+
export class NotExtendedError extends HttpError {
|
|
397
|
+
constructor(message = 'Not Extended', options: HttpErrorOptions = {}) {
|
|
398
|
+
super(510, message, { code: 'NOT_EXTENDED', expose: false, ...options });
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* 511 Network Authentication Required
|
|
404
|
+
*/
|
|
405
|
+
export class NetworkAuthRequiredError extends HttpError {
|
|
406
|
+
constructor(message = 'Network Authentication Required', options: HttpErrorOptions = {}) {
|
|
407
|
+
super(511, message, { code: 'NETWORK_AUTH_REQUIRED', expose: false, ...options });
|
|
408
|
+
}
|
|
409
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nextrush/errors
|
|
3
|
+
*
|
|
4
|
+
* Standardized error handling for NextRush framework.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Base classes
|
|
10
|
+
export { HttpError, NextRushError, getHttpStatusMessage } from './base';
|
|
11
|
+
|
|
12
|
+
// HTTP errors - 4xx
|
|
13
|
+
export {
|
|
14
|
+
BadRequestError,
|
|
15
|
+
ConflictError,
|
|
16
|
+
ExpectationFailedError,
|
|
17
|
+
FailedDependencyError,
|
|
18
|
+
ForbiddenError,
|
|
19
|
+
GoneError,
|
|
20
|
+
ImATeapotError,
|
|
21
|
+
LengthRequiredError,
|
|
22
|
+
LockedError,
|
|
23
|
+
MethodNotAllowedError,
|
|
24
|
+
NotAcceptableError,
|
|
25
|
+
NotFoundError,
|
|
26
|
+
PayloadTooLargeError,
|
|
27
|
+
PaymentRequiredError,
|
|
28
|
+
PreconditionFailedError,
|
|
29
|
+
PreconditionRequiredError,
|
|
30
|
+
ProxyAuthRequiredError,
|
|
31
|
+
RangeNotSatisfiableError,
|
|
32
|
+
RequestHeaderFieldsTooLargeError,
|
|
33
|
+
RequestTimeoutError,
|
|
34
|
+
TooEarlyError,
|
|
35
|
+
TooManyRequestsError,
|
|
36
|
+
UnauthorizedError,
|
|
37
|
+
UnavailableForLegalReasonsError,
|
|
38
|
+
UnprocessableEntityError,
|
|
39
|
+
UnsupportedMediaTypeError,
|
|
40
|
+
UpgradeRequiredError,
|
|
41
|
+
UriTooLongError,
|
|
42
|
+
type HttpErrorOptions,
|
|
43
|
+
} from './http-errors';
|
|
44
|
+
|
|
45
|
+
// HTTP errors - 5xx
|
|
46
|
+
export {
|
|
47
|
+
BadGatewayError,
|
|
48
|
+
GatewayTimeoutError,
|
|
49
|
+
HttpVersionNotSupportedError,
|
|
50
|
+
InsufficientStorageError,
|
|
51
|
+
InternalServerError,
|
|
52
|
+
LoopDetectedError,
|
|
53
|
+
NetworkAuthRequiredError,
|
|
54
|
+
NotExtendedError,
|
|
55
|
+
NotImplementedError,
|
|
56
|
+
ServiceUnavailableError,
|
|
57
|
+
VariantAlsoNegotiatesError,
|
|
58
|
+
} from './http-errors';
|
|
59
|
+
|
|
60
|
+
// Validation errors
|
|
61
|
+
export {
|
|
62
|
+
InvalidEmailError,
|
|
63
|
+
InvalidUrlError,
|
|
64
|
+
LengthError,
|
|
65
|
+
PatternError,
|
|
66
|
+
RangeValidationError,
|
|
67
|
+
RequiredFieldError,
|
|
68
|
+
TypeMismatchError,
|
|
69
|
+
ValidationError,
|
|
70
|
+
type ValidationIssue,
|
|
71
|
+
} from './validation';
|
|
72
|
+
|
|
73
|
+
// Factory functions
|
|
74
|
+
export {
|
|
75
|
+
badGateway,
|
|
76
|
+
badRequest,
|
|
77
|
+
conflict,
|
|
78
|
+
createError,
|
|
79
|
+
forbidden,
|
|
80
|
+
gatewayTimeout,
|
|
81
|
+
getErrorStatus,
|
|
82
|
+
getSafeErrorMessage,
|
|
83
|
+
internalError,
|
|
84
|
+
isHttpError,
|
|
85
|
+
methodNotAllowed,
|
|
86
|
+
notFound,
|
|
87
|
+
serviceUnavailable,
|
|
88
|
+
tooManyRequests,
|
|
89
|
+
unauthorized,
|
|
90
|
+
unprocessableEntity,
|
|
91
|
+
} from './factory';
|
|
92
|
+
|
|
93
|
+
// Middleware
|
|
94
|
+
export {
|
|
95
|
+
catchAsync,
|
|
96
|
+
errorHandler,
|
|
97
|
+
notFoundHandler,
|
|
98
|
+
type ErrorContext,
|
|
99
|
+
type ErrorHandlerOptions,
|
|
100
|
+
type ErrorMiddleware,
|
|
101
|
+
} from './middleware';
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nextrush/errors - Error Handler Middleware
|
|
3
|
+
*
|
|
4
|
+
* Middleware for handling errors in NextRush applications.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Context, Middleware, Next } from '@nextrush/types';
|
|
10
|
+
import { HttpError, NextRushError, getHttpStatusMessage } from './base';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Minimal context interface for error handling.
|
|
14
|
+
*
|
|
15
|
+
* @deprecated Use `Context` from `@nextrush/types` instead.
|
|
16
|
+
* Kept for backward compatibility — will be removed in v4.
|
|
17
|
+
*/
|
|
18
|
+
export interface ErrorContext {
|
|
19
|
+
method: string;
|
|
20
|
+
path: string;
|
|
21
|
+
status: number;
|
|
22
|
+
json: (data: unknown) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Error handler middleware function type.
|
|
27
|
+
*
|
|
28
|
+
* @deprecated Use `Middleware` from `@nextrush/types` instead.
|
|
29
|
+
* Kept for backward compatibility — will be removed in v4.
|
|
30
|
+
*/
|
|
31
|
+
export type ErrorMiddleware = Middleware;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Error handler options
|
|
35
|
+
*/
|
|
36
|
+
export interface ErrorHandlerOptions {
|
|
37
|
+
/** Include stack trace in development */
|
|
38
|
+
includeStack?: boolean;
|
|
39
|
+
|
|
40
|
+
/** Custom error logger */
|
|
41
|
+
logger?: (error: Error, ctx: Context) => void;
|
|
42
|
+
|
|
43
|
+
/** Custom error transformer */
|
|
44
|
+
transform?: (error: Error, ctx: Context) => Record<string, unknown>;
|
|
45
|
+
|
|
46
|
+
/** Handle specific error types */
|
|
47
|
+
handlers?: Map<new (...args: unknown[]) => Error, (error: Error, ctx: Context) => void>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Default error logger
|
|
52
|
+
*/
|
|
53
|
+
function defaultLogger(error: Error, ctx: Context): void {
|
|
54
|
+
const status = error instanceof HttpError ? error.status : 500;
|
|
55
|
+
|
|
56
|
+
if (status >= 500) {
|
|
57
|
+
console.error(`[ERROR] ${ctx.method} ${ctx.path}:`, error);
|
|
58
|
+
} else {
|
|
59
|
+
console.warn(`[WARN] ${ctx.method} ${ctx.path}: ${error.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Create error handler middleware
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const app = createApp();
|
|
69
|
+
*
|
|
70
|
+
* // Add error handler first
|
|
71
|
+
* app.use(errorHandler({
|
|
72
|
+
* includeStack: process.env.NODE_ENV !== 'production',
|
|
73
|
+
* logger: (err, ctx) => myLogger.error(err),
|
|
74
|
+
* }));
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function errorHandler(options: ErrorHandlerOptions = {}): Middleware {
|
|
78
|
+
const { includeStack = false, logger = defaultLogger, transform, handlers } = options;
|
|
79
|
+
|
|
80
|
+
return async (ctx: Context, next: Next): Promise<void> => {
|
|
81
|
+
try {
|
|
82
|
+
await next();
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
85
|
+
|
|
86
|
+
// Log the error
|
|
87
|
+
logger(err, ctx);
|
|
88
|
+
|
|
89
|
+
// Check for custom handlers
|
|
90
|
+
if (handlers) {
|
|
91
|
+
for (const [ErrorType, handler] of handlers) {
|
|
92
|
+
if (err instanceof ErrorType) {
|
|
93
|
+
handler(err, ctx);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Determine status code
|
|
100
|
+
let status = 500;
|
|
101
|
+
let expose = false;
|
|
102
|
+
let code = 'INTERNAL_ERROR';
|
|
103
|
+
let details: Record<string, unknown> | undefined;
|
|
104
|
+
|
|
105
|
+
if (err instanceof HttpError || err instanceof NextRushError) {
|
|
106
|
+
status = err.status;
|
|
107
|
+
expose = err.expose;
|
|
108
|
+
code = err.code;
|
|
109
|
+
details = err.details;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
ctx.status = status;
|
|
113
|
+
|
|
114
|
+
// Build response body
|
|
115
|
+
let body: Record<string, unknown>;
|
|
116
|
+
|
|
117
|
+
if (transform) {
|
|
118
|
+
body = transform(err, ctx);
|
|
119
|
+
} else {
|
|
120
|
+
body = {
|
|
121
|
+
error: expose ? err.name : getHttpStatusMessage(status),
|
|
122
|
+
message: expose ? err.message : 'Internal Server Error',
|
|
123
|
+
code,
|
|
124
|
+
status,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (expose && details) {
|
|
128
|
+
body.details = details;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (includeStack && err.stack) {
|
|
132
|
+
body.stack = err.stack.split('\n').map((line) => line.trim());
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
ctx.json(body);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Not found handler middleware - catches unhandled requests
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* // Add at the end of middleware chain
|
|
147
|
+
* app.use(notFoundHandler());
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export function notFoundHandler(message = 'Not Found'): Middleware {
|
|
151
|
+
return async (ctx: Context, next: Next): Promise<void> => {
|
|
152
|
+
await next();
|
|
153
|
+
// Only handle if no response was sent and status indicates unhandled
|
|
154
|
+
if (!ctx.responded && ctx.status === 404) {
|
|
155
|
+
ctx.json({
|
|
156
|
+
error: 'NotFoundError',
|
|
157
|
+
message,
|
|
158
|
+
code: 'NOT_FOUND',
|
|
159
|
+
status: 404,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Catch async errors wrapper for route handlers
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* router.get('/users/:id', catchAsync(async (ctx) => {
|
|
171
|
+
* const user = await db.users.findById(ctx.params.id);
|
|
172
|
+
* if (!user) throw new NotFoundError('User not found');
|
|
173
|
+
* ctx.json(user);
|
|
174
|
+
* }));
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* @deprecated This wrapper is redundant — async errors propagate naturally
|
|
178
|
+
* through the middleware chain and are caught by `errorHandler()`. Use the
|
|
179
|
+
* handler directly instead.
|
|
180
|
+
*/
|
|
181
|
+
export function catchAsync(handler: (ctx: Context, next: Next) => Promise<void>): Middleware {
|
|
182
|
+
return handler;
|
|
183
|
+
}
|