@web-ts-toolkit/http-errors 0.31.5 → 0.33.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/README.md +99 -1
- package/index.d.mts +163 -22
- package/index.d.ts +163 -22
- package/index.js +298 -58
- package/index.mjs +283 -57
- package/package.json +8 -4
package/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/status.ts
|
|
2
|
+
import assert from "assert";
|
|
2
3
|
import { STATUS_CODES } from "http";
|
|
3
|
-
var
|
|
4
|
+
var canonicalStatusLookup = Object.freeze({
|
|
4
5
|
400: "INVALID_ARGUMENT",
|
|
5
6
|
401: "UNAUTHENTICATED",
|
|
6
7
|
403: "PERMISSION_DENIED",
|
|
@@ -12,26 +13,77 @@ var canonicalStatusByHttpStatus = {
|
|
|
12
13
|
501: "UNIMPLEMENTED",
|
|
13
14
|
503: "UNAVAILABLE",
|
|
14
15
|
504: "DEADLINE_EXCEEDED"
|
|
15
|
-
};
|
|
16
|
-
var
|
|
16
|
+
});
|
|
17
|
+
var canonicalStatusByHttpStatus = Object.freeze({
|
|
18
|
+
...canonicalStatusLookup
|
|
19
|
+
});
|
|
20
|
+
var getCanonicalStatus = (statusCode) => canonicalStatusLookup[statusCode] || "UNKNOWN";
|
|
17
21
|
var getStatusTitle = (statusCode) => STATUS_CODES[statusCode] || "Unknown Error";
|
|
22
|
+
var isHttpErrorStatusCode = (statusCode) => typeof statusCode === "number" && Number.isFinite(statusCode) && Number.isInteger(statusCode) && statusCode >= 400 && statusCode <= 599;
|
|
23
|
+
var validateHttpErrorStatusCode = (statusCode, source = "statusCode") => {
|
|
24
|
+
assert.ok(
|
|
25
|
+
isHttpErrorStatusCode(statusCode),
|
|
26
|
+
`${source} must be an integer HTTP error status code between 400 and 599`
|
|
27
|
+
);
|
|
28
|
+
return statusCode;
|
|
29
|
+
};
|
|
30
|
+
var validateClientErrorStatusCode = (statusCode, source = "statusCode") => {
|
|
31
|
+
validateHttpErrorStatusCode(statusCode, source);
|
|
32
|
+
assert.ok(typeof statusCode === "number" && statusCode <= 499, `${source} must be a 4xx HTTP error status code`);
|
|
33
|
+
return statusCode;
|
|
34
|
+
};
|
|
35
|
+
var validateServerErrorStatusCode = (statusCode, source = "statusCode") => {
|
|
36
|
+
validateHttpErrorStatusCode(statusCode, source);
|
|
37
|
+
assert.ok(typeof statusCode === "number" && statusCode >= 500, `${source} must be a 5xx HTTP error status code`);
|
|
38
|
+
return statusCode;
|
|
39
|
+
};
|
|
18
40
|
|
|
19
41
|
// src/serialize.ts
|
|
20
42
|
var RFC_9457_DEFAULT_TYPE = "about:blank";
|
|
43
|
+
var RFC_9457_VALIDATION_ERROR_OPTIONAL_FIELDS = ["pointer", "parameter", "header"];
|
|
21
44
|
var toAip193ErrorDetails = (error, fallbackDomain) => {
|
|
22
45
|
const status = error.status ?? getCanonicalStatus(error.statusCode);
|
|
23
46
|
return [
|
|
24
47
|
createAip193ErrorInfoDetail(error.reason ?? status, error.domain ?? fallbackDomain, error.metadata),
|
|
25
|
-
...error.errors !== void 0 ? [{ type: "bad_request", errors: error.errors }] : [],
|
|
48
|
+
...error.errors !== void 0 ? [{ type: "bad_request", errors: Array.isArray(error.errors) ? [...error.errors] : error.errors }] : [],
|
|
26
49
|
...error.details ?? []
|
|
27
50
|
];
|
|
28
51
|
};
|
|
29
|
-
var toRfc9457Errors = (errors) => Array.isArray(errors) ? errors : void 0;
|
|
52
|
+
var toRfc9457Errors = (errors) => Array.isArray(errors) && errors.length > 0 ? [...errors] : void 0;
|
|
53
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
54
|
+
var toRfc9457ValidationError = (value) => {
|
|
55
|
+
if (!isRecord(value) || !Object.prototype.hasOwnProperty.call(value, "detail") || typeof value.detail !== "string") {
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
const validationError = {
|
|
59
|
+
detail: value.detail
|
|
60
|
+
};
|
|
61
|
+
for (const field of RFC_9457_VALIDATION_ERROR_OPTIONAL_FIELDS) {
|
|
62
|
+
if (Object.prototype.hasOwnProperty.call(value, field)) {
|
|
63
|
+
const fieldValue = value[field];
|
|
64
|
+
if (typeof fieldValue !== "string") {
|
|
65
|
+
return void 0;
|
|
66
|
+
}
|
|
67
|
+
validationError[field] = fieldValue;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return validationError;
|
|
71
|
+
};
|
|
72
|
+
var toRfc9457ValidationErrors = (errors) => {
|
|
73
|
+
if (!Array.isArray(errors)) {
|
|
74
|
+
return void 0;
|
|
75
|
+
}
|
|
76
|
+
const validationErrors = errors.flatMap((entry) => {
|
|
77
|
+
const validationError = toRfc9457ValidationError(entry);
|
|
78
|
+
return validationError ? [validationError] : [];
|
|
79
|
+
});
|
|
80
|
+
return validationErrors.length > 0 ? validationErrors : void 0;
|
|
81
|
+
};
|
|
30
82
|
var createAip193ErrorInfoDetail = (reason, domain, metadata) => ({
|
|
31
83
|
type: "error_info",
|
|
32
84
|
reason,
|
|
33
85
|
domain,
|
|
34
|
-
...metadata ? { metadata } : {}
|
|
86
|
+
...metadata ? { metadata: { ...metadata } } : {}
|
|
35
87
|
});
|
|
36
88
|
var toAip193ErrorPayload = (error, fallbackDomain = "http-errors") => {
|
|
37
89
|
const status = error.status ?? getCanonicalStatus(error.statusCode);
|
|
@@ -45,7 +97,7 @@ var toAip193ErrorPayload = (error, fallbackDomain = "http-errors") => {
|
|
|
45
97
|
}
|
|
46
98
|
};
|
|
47
99
|
};
|
|
48
|
-
|
|
100
|
+
function toRfc9457ErrorPayload(error) {
|
|
49
101
|
const errors = toRfc9457Errors(error.errors);
|
|
50
102
|
return {
|
|
51
103
|
type: error.type ?? RFC_9457_DEFAULT_TYPE,
|
|
@@ -55,67 +107,237 @@ var toRfc9457ErrorPayload = (error) => {
|
|
|
55
107
|
...error.instance ? { instance: error.instance } : {},
|
|
56
108
|
...errors ? { errors } : {}
|
|
57
109
|
};
|
|
110
|
+
}
|
|
111
|
+
var toRfc9457ValidationErrorPayload = (error) => {
|
|
112
|
+
const errors = toRfc9457ValidationErrors(error.errors);
|
|
113
|
+
return {
|
|
114
|
+
type: error.type ?? RFC_9457_DEFAULT_TYPE,
|
|
115
|
+
title: error.title ?? getStatusTitle(error.statusCode),
|
|
116
|
+
status: error.statusCode,
|
|
117
|
+
detail: error.message,
|
|
118
|
+
...error.instance ? { instance: error.instance } : {},
|
|
119
|
+
...errors ? { errors } : {}
|
|
120
|
+
};
|
|
58
121
|
};
|
|
59
|
-
var toRfc9457ValidationErrorPayload = (error) => toRfc9457ErrorPayload(error);
|
|
60
122
|
|
|
61
123
|
// src/base.ts
|
|
62
124
|
import { toStringRecord } from "@web-ts-toolkit/utils";
|
|
63
125
|
|
|
64
|
-
// src/
|
|
65
|
-
var
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
126
|
+
// src/status-definitions.ts
|
|
127
|
+
var httpErrorStatusDefinitions = {
|
|
128
|
+
BadRequestError: {
|
|
129
|
+
statusCode: 400,
|
|
130
|
+
category: "client",
|
|
131
|
+
message: "The server cannot process the request due to a client error"
|
|
132
|
+
},
|
|
133
|
+
UnauthorizedError: {
|
|
134
|
+
statusCode: 401,
|
|
135
|
+
category: "client",
|
|
136
|
+
message: "The user is not authorized"
|
|
137
|
+
},
|
|
138
|
+
ForbiddenError: {
|
|
139
|
+
statusCode: 403,
|
|
140
|
+
category: "client",
|
|
141
|
+
message: "The server refused to authorize the request"
|
|
142
|
+
},
|
|
143
|
+
NotFoundError: {
|
|
144
|
+
statusCode: 404,
|
|
145
|
+
category: "client",
|
|
146
|
+
message: "The server did not find a current representation for the target resource"
|
|
147
|
+
},
|
|
148
|
+
MethodNotAllowedError: {
|
|
149
|
+
statusCode: 405,
|
|
150
|
+
category: "client",
|
|
151
|
+
message: "The method received is not allowed"
|
|
152
|
+
},
|
|
153
|
+
NotAcceptableError: {
|
|
154
|
+
statusCode: 406,
|
|
155
|
+
category: "client",
|
|
156
|
+
message: "The request is not acceptable to the user agent"
|
|
157
|
+
},
|
|
158
|
+
ProxyAuthRequiredError: {
|
|
159
|
+
statusCode: 407,
|
|
160
|
+
category: "client",
|
|
161
|
+
message: "The client needs to authenticate itself in order to use a proxy"
|
|
162
|
+
},
|
|
163
|
+
RequestTimeoutError: {
|
|
164
|
+
statusCode: 408,
|
|
165
|
+
category: "client",
|
|
166
|
+
message: "The request was not completed in the expected time"
|
|
167
|
+
},
|
|
168
|
+
ConflictError: {
|
|
169
|
+
statusCode: 409,
|
|
170
|
+
category: "client",
|
|
171
|
+
message: "The request was not completed due to a conflict with the target resource"
|
|
172
|
+
},
|
|
173
|
+
GoneError: {
|
|
174
|
+
statusCode: 410,
|
|
175
|
+
category: "client",
|
|
176
|
+
message: "The target resource is no longer available at the origin server"
|
|
177
|
+
},
|
|
178
|
+
LengthRequiredError: {
|
|
179
|
+
statusCode: 411,
|
|
180
|
+
category: "client",
|
|
181
|
+
message: "The server refuses to accept the request without a defined Content-Length"
|
|
182
|
+
},
|
|
183
|
+
PreconditionFailedError: {
|
|
184
|
+
statusCode: 412,
|
|
185
|
+
category: "client",
|
|
186
|
+
message: "One or more conditions given in the request header fields evaluated to false"
|
|
187
|
+
},
|
|
188
|
+
PayloadTooLargeError: {
|
|
189
|
+
statusCode: 413,
|
|
190
|
+
category: "client",
|
|
191
|
+
message: "The request payload is too large"
|
|
192
|
+
},
|
|
193
|
+
UriTooLongError: {
|
|
194
|
+
statusCode: 414,
|
|
195
|
+
category: "client",
|
|
196
|
+
message: "The request target is too long"
|
|
197
|
+
},
|
|
198
|
+
UnsupportedMediaTypeError: {
|
|
199
|
+
statusCode: 415,
|
|
200
|
+
category: "client",
|
|
201
|
+
message: "The payload is in a format not supported"
|
|
202
|
+
},
|
|
203
|
+
RequestedRangeNotSatisfiableError: {
|
|
204
|
+
statusCode: 416,
|
|
205
|
+
category: "client",
|
|
206
|
+
message: "None of the ranges in the request's Range header field overlap the current extent of the selected resource"
|
|
207
|
+
},
|
|
208
|
+
ExpectationFailedError: {
|
|
209
|
+
statusCode: 417,
|
|
210
|
+
category: "client",
|
|
211
|
+
message: "The expectation given in the request's Expect header field could not be met"
|
|
212
|
+
},
|
|
213
|
+
TeapotError: {
|
|
214
|
+
statusCode: 418,
|
|
215
|
+
category: "client",
|
|
216
|
+
message: "I'm a teapot"
|
|
217
|
+
},
|
|
218
|
+
MisdirectedRequestError: {
|
|
219
|
+
statusCode: 421,
|
|
220
|
+
category: "client",
|
|
221
|
+
message: "The request was directed at a server that is not able to produce a response"
|
|
222
|
+
},
|
|
223
|
+
UnprocessableEntityError: {
|
|
224
|
+
statusCode: 422,
|
|
225
|
+
category: "client",
|
|
226
|
+
message: "The server is unable to process the request"
|
|
227
|
+
},
|
|
228
|
+
LockedError: {
|
|
229
|
+
statusCode: 423,
|
|
230
|
+
category: "client",
|
|
231
|
+
message: "The source or destination resource of a method is locked"
|
|
232
|
+
},
|
|
233
|
+
FailedDependencyError: {
|
|
234
|
+
statusCode: 424,
|
|
235
|
+
category: "client",
|
|
236
|
+
message: "The requested action depended on another action"
|
|
237
|
+
},
|
|
238
|
+
UpgradeRequiredError: {
|
|
239
|
+
statusCode: 426,
|
|
240
|
+
category: "client",
|
|
241
|
+
message: "This service requires use of a different protocol"
|
|
242
|
+
},
|
|
243
|
+
PreconditionRequiredError: {
|
|
244
|
+
statusCode: 428,
|
|
245
|
+
category: "client",
|
|
246
|
+
message: "This request is required to be conditional"
|
|
247
|
+
},
|
|
248
|
+
TooManyRequestsError: {
|
|
249
|
+
statusCode: 429,
|
|
250
|
+
category: "client",
|
|
251
|
+
message: "The user has sent too many requests in a given amount of time"
|
|
252
|
+
},
|
|
253
|
+
RequestHeaderFieldsTooLargeError: {
|
|
254
|
+
statusCode: 431,
|
|
255
|
+
category: "client",
|
|
256
|
+
message: "Request header fields too large"
|
|
257
|
+
},
|
|
258
|
+
UnavailableForLegalReasonsError: {
|
|
259
|
+
statusCode: 451,
|
|
260
|
+
category: "client",
|
|
261
|
+
message: "Denied access due to a consequence of a legal demand"
|
|
262
|
+
},
|
|
263
|
+
InternalServerError: {
|
|
264
|
+
statusCode: 500,
|
|
265
|
+
category: "server",
|
|
266
|
+
message: "The server encountered an unexpected condition"
|
|
267
|
+
},
|
|
268
|
+
NotImplementedError: {
|
|
269
|
+
statusCode: 501,
|
|
270
|
+
category: "server",
|
|
271
|
+
message: "The server does not support the functionality required to fulfill the request"
|
|
272
|
+
},
|
|
273
|
+
BadGatewayError: {
|
|
274
|
+
statusCode: 502,
|
|
275
|
+
category: "server",
|
|
276
|
+
message: "The server received an invalid response from an upstream server"
|
|
277
|
+
},
|
|
278
|
+
ServiceUnavailableError: {
|
|
279
|
+
statusCode: 503,
|
|
280
|
+
category: "server",
|
|
281
|
+
message: "The server is temporarily unable to handle the request"
|
|
282
|
+
},
|
|
283
|
+
GatewayTimeoutError: {
|
|
284
|
+
statusCode: 504,
|
|
285
|
+
category: "server",
|
|
286
|
+
message: "The server did not receive a timely response from an upstream server"
|
|
287
|
+
},
|
|
288
|
+
HttpVersionNotSupportedError: {
|
|
289
|
+
statusCode: 505,
|
|
290
|
+
category: "server",
|
|
291
|
+
message: "The server does not support the HTTP protocol version used in the request"
|
|
292
|
+
},
|
|
293
|
+
VariantAlsoNegotiatesError: {
|
|
294
|
+
statusCode: 506,
|
|
295
|
+
category: "server",
|
|
296
|
+
message: "The server has an internal configuration error"
|
|
297
|
+
},
|
|
298
|
+
InsufficientStorageError: {
|
|
299
|
+
statusCode: 507,
|
|
300
|
+
category: "server",
|
|
301
|
+
message: "The server is unable to store the representation needed to complete the request"
|
|
302
|
+
},
|
|
303
|
+
LoopDetectedError: {
|
|
304
|
+
statusCode: 508,
|
|
305
|
+
category: "server",
|
|
306
|
+
message: "The server detected an infinite loop while processing the request"
|
|
307
|
+
},
|
|
308
|
+
NotExtendedError: {
|
|
309
|
+
statusCode: 510,
|
|
310
|
+
category: "server",
|
|
311
|
+
message: "Further extensions to the request are required for the server to fulfill it"
|
|
312
|
+
},
|
|
313
|
+
NetworkAuthenticationRequiredError: {
|
|
314
|
+
statusCode: 511,
|
|
315
|
+
category: "server",
|
|
316
|
+
message: "The client needs to authenticate to gain network access"
|
|
317
|
+
}
|
|
104
318
|
};
|
|
319
|
+
|
|
320
|
+
// src/messages.ts
|
|
321
|
+
var messages = Object.freeze(
|
|
322
|
+
Object.fromEntries(
|
|
323
|
+
Object.values(httpErrorStatusDefinitions).map(({ statusCode, message }) => [statusCode, message])
|
|
324
|
+
)
|
|
325
|
+
);
|
|
105
326
|
var getDefaultMessage = (statusCode) => messages[statusCode] || messages[500];
|
|
106
327
|
|
|
107
328
|
// src/base.ts
|
|
108
329
|
var ErrorCtor = Error;
|
|
109
330
|
var HttpError = class extends Error {
|
|
110
331
|
constructor(statusCode = 500, message, options = {}) {
|
|
111
|
-
|
|
332
|
+
const validatedStatusCode = validateHttpErrorStatusCode(statusCode);
|
|
333
|
+
super(message ?? getDefaultMessage(validatedStatusCode), options);
|
|
112
334
|
const { status, reason, domain, metadata, details, errors, type, title, instance } = options;
|
|
113
335
|
if (ErrorCtor.captureStackTrace) {
|
|
114
336
|
ErrorCtor.captureStackTrace(this, this.constructor);
|
|
115
337
|
}
|
|
116
338
|
this.name = this.constructor.name;
|
|
117
|
-
this.statusCode =
|
|
118
|
-
this.status = status ?? getCanonicalStatus(
|
|
339
|
+
this.statusCode = validatedStatusCode;
|
|
340
|
+
this.status = status ?? getCanonicalStatus(validatedStatusCode);
|
|
119
341
|
this.date = /* @__PURE__ */ new Date();
|
|
120
342
|
if (reason !== void 0) {
|
|
121
343
|
this.reason = reason;
|
|
@@ -125,13 +347,13 @@ var HttpError = class extends Error {
|
|
|
125
347
|
}
|
|
126
348
|
const normalizedMetadata = toStringRecord(metadata);
|
|
127
349
|
if (normalizedMetadata !== void 0) {
|
|
128
|
-
this.metadata = normalizedMetadata;
|
|
350
|
+
this.metadata = Object.freeze(normalizedMetadata);
|
|
129
351
|
}
|
|
130
352
|
if (details !== void 0) {
|
|
131
|
-
this.details = details;
|
|
353
|
+
this.details = Object.freeze([...details]);
|
|
132
354
|
}
|
|
133
355
|
if (errors !== void 0) {
|
|
134
|
-
this.errors = errors;
|
|
356
|
+
this.errors = Array.isArray(errors) ? Object.freeze([...errors]) : errors;
|
|
135
357
|
}
|
|
136
358
|
if (type !== void 0) {
|
|
137
359
|
this.type = type;
|
|
@@ -146,12 +368,12 @@ var HttpError = class extends Error {
|
|
|
146
368
|
};
|
|
147
369
|
var ClientError = class extends HttpError {
|
|
148
370
|
constructor(statusCode = 400, message, options = {}) {
|
|
149
|
-
super(statusCode, message, options);
|
|
371
|
+
super(validateClientErrorStatusCode(statusCode), message, options);
|
|
150
372
|
}
|
|
151
373
|
};
|
|
152
374
|
var ServerError = class extends HttpError {
|
|
153
375
|
constructor(statusCode = 500, message, options = {}) {
|
|
154
|
-
super(statusCode, message, options);
|
|
376
|
+
super(validateServerErrorStatusCode(statusCode), message, options);
|
|
155
377
|
}
|
|
156
378
|
};
|
|
157
379
|
|
|
@@ -394,7 +616,11 @@ export {
|
|
|
394
616
|
createAip193ErrorInfoDetail,
|
|
395
617
|
getCanonicalStatus,
|
|
396
618
|
getStatusTitle,
|
|
619
|
+
isHttpErrorStatusCode,
|
|
397
620
|
toAip193ErrorPayload,
|
|
398
621
|
toRfc9457ErrorPayload,
|
|
399
|
-
toRfc9457ValidationErrorPayload
|
|
622
|
+
toRfc9457ValidationErrorPayload,
|
|
623
|
+
validateClientErrorStatusCode,
|
|
624
|
+
validateHttpErrorStatusCode,
|
|
625
|
+
validateServerErrorStatusCode
|
|
400
626
|
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@web-ts-toolkit/http-errors",
|
|
3
3
|
"description": "Typed HTTP error classes and payload helpers for backend APIs",
|
|
4
4
|
"homepage": "https://web-ts-toolkit.pages.dev/docs/packages/http-errors",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.33.0",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"keywords": [
|
|
8
8
|
"http-errors",
|
|
@@ -16,17 +16,21 @@
|
|
|
16
16
|
"types": "./index.d.ts",
|
|
17
17
|
"exports": {
|
|
18
18
|
".": {
|
|
19
|
-
"types":
|
|
19
|
+
"types": {
|
|
20
|
+
"import": "./index.d.mts",
|
|
21
|
+
"require": "./index.d.ts",
|
|
22
|
+
"default": "./index.d.ts"
|
|
23
|
+
},
|
|
20
24
|
"import": "./index.mjs",
|
|
21
25
|
"require": "./index.js",
|
|
22
26
|
"default": "./index.js"
|
|
23
27
|
}
|
|
24
28
|
},
|
|
25
29
|
"engines": {
|
|
26
|
-
"node": ">=
|
|
30
|
+
"node": ">=22"
|
|
27
31
|
},
|
|
28
32
|
"dependencies": {
|
|
29
|
-
"@web-ts-toolkit/utils": "0.
|
|
33
|
+
"@web-ts-toolkit/utils": "0.33.0"
|
|
30
34
|
},
|
|
31
35
|
"files": [
|
|
32
36
|
"**/*",
|