@web-ts-toolkit/http-errors 0.32.0 → 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/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -65,15 +75,20 @@ __export(index_exports, {
65
75
  createAip193ErrorInfoDetail: () => createAip193ErrorInfoDetail,
66
76
  getCanonicalStatus: () => getCanonicalStatus,
67
77
  getStatusTitle: () => getStatusTitle,
78
+ isHttpErrorStatusCode: () => isHttpErrorStatusCode,
68
79
  toAip193ErrorPayload: () => toAip193ErrorPayload,
69
80
  toRfc9457ErrorPayload: () => toRfc9457ErrorPayload,
70
- toRfc9457ValidationErrorPayload: () => toRfc9457ValidationErrorPayload
81
+ toRfc9457ValidationErrorPayload: () => toRfc9457ValidationErrorPayload,
82
+ validateClientErrorStatusCode: () => validateClientErrorStatusCode,
83
+ validateHttpErrorStatusCode: () => validateHttpErrorStatusCode,
84
+ validateServerErrorStatusCode: () => validateServerErrorStatusCode
71
85
  });
72
86
  module.exports = __toCommonJS(index_exports);
73
87
 
74
88
  // src/status.ts
89
+ var import_node_assert = __toESM(require("assert"));
75
90
  var import_node_http = require("http");
76
- var canonicalStatusByHttpStatus = {
91
+ var canonicalStatusLookup = Object.freeze({
77
92
  400: "INVALID_ARGUMENT",
78
93
  401: "UNAUTHENTICATED",
79
94
  403: "PERMISSION_DENIED",
@@ -85,26 +100,77 @@ var canonicalStatusByHttpStatus = {
85
100
  501: "UNIMPLEMENTED",
86
101
  503: "UNAVAILABLE",
87
102
  504: "DEADLINE_EXCEEDED"
88
- };
89
- var getCanonicalStatus = (statusCode) => canonicalStatusByHttpStatus[statusCode] || "UNKNOWN";
103
+ });
104
+ var canonicalStatusByHttpStatus = Object.freeze({
105
+ ...canonicalStatusLookup
106
+ });
107
+ var getCanonicalStatus = (statusCode) => canonicalStatusLookup[statusCode] || "UNKNOWN";
90
108
  var getStatusTitle = (statusCode) => import_node_http.STATUS_CODES[statusCode] || "Unknown Error";
109
+ var isHttpErrorStatusCode = (statusCode) => typeof statusCode === "number" && Number.isFinite(statusCode) && Number.isInteger(statusCode) && statusCode >= 400 && statusCode <= 599;
110
+ var validateHttpErrorStatusCode = (statusCode, source = "statusCode") => {
111
+ import_node_assert.default.ok(
112
+ isHttpErrorStatusCode(statusCode),
113
+ `${source} must be an integer HTTP error status code between 400 and 599`
114
+ );
115
+ return statusCode;
116
+ };
117
+ var validateClientErrorStatusCode = (statusCode, source = "statusCode") => {
118
+ validateHttpErrorStatusCode(statusCode, source);
119
+ import_node_assert.default.ok(typeof statusCode === "number" && statusCode <= 499, `${source} must be a 4xx HTTP error status code`);
120
+ return statusCode;
121
+ };
122
+ var validateServerErrorStatusCode = (statusCode, source = "statusCode") => {
123
+ validateHttpErrorStatusCode(statusCode, source);
124
+ import_node_assert.default.ok(typeof statusCode === "number" && statusCode >= 500, `${source} must be a 5xx HTTP error status code`);
125
+ return statusCode;
126
+ };
91
127
 
92
128
  // src/serialize.ts
93
129
  var RFC_9457_DEFAULT_TYPE = "about:blank";
130
+ var RFC_9457_VALIDATION_ERROR_OPTIONAL_FIELDS = ["pointer", "parameter", "header"];
94
131
  var toAip193ErrorDetails = (error, fallbackDomain) => {
95
132
  const status = error.status ?? getCanonicalStatus(error.statusCode);
96
133
  return [
97
134
  createAip193ErrorInfoDetail(error.reason ?? status, error.domain ?? fallbackDomain, error.metadata),
98
- ...error.errors !== void 0 ? [{ type: "bad_request", errors: error.errors }] : [],
135
+ ...error.errors !== void 0 ? [{ type: "bad_request", errors: Array.isArray(error.errors) ? [...error.errors] : error.errors }] : [],
99
136
  ...error.details ?? []
100
137
  ];
101
138
  };
102
- var toRfc9457Errors = (errors) => Array.isArray(errors) ? errors : void 0;
139
+ var toRfc9457Errors = (errors) => Array.isArray(errors) && errors.length > 0 ? [...errors] : void 0;
140
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
141
+ var toRfc9457ValidationError = (value) => {
142
+ if (!isRecord(value) || !Object.prototype.hasOwnProperty.call(value, "detail") || typeof value.detail !== "string") {
143
+ return void 0;
144
+ }
145
+ const validationError = {
146
+ detail: value.detail
147
+ };
148
+ for (const field of RFC_9457_VALIDATION_ERROR_OPTIONAL_FIELDS) {
149
+ if (Object.prototype.hasOwnProperty.call(value, field)) {
150
+ const fieldValue = value[field];
151
+ if (typeof fieldValue !== "string") {
152
+ return void 0;
153
+ }
154
+ validationError[field] = fieldValue;
155
+ }
156
+ }
157
+ return validationError;
158
+ };
159
+ var toRfc9457ValidationErrors = (errors) => {
160
+ if (!Array.isArray(errors)) {
161
+ return void 0;
162
+ }
163
+ const validationErrors = errors.flatMap((entry) => {
164
+ const validationError = toRfc9457ValidationError(entry);
165
+ return validationError ? [validationError] : [];
166
+ });
167
+ return validationErrors.length > 0 ? validationErrors : void 0;
168
+ };
103
169
  var createAip193ErrorInfoDetail = (reason, domain, metadata) => ({
104
170
  type: "error_info",
105
171
  reason,
106
172
  domain,
107
- ...metadata ? { metadata } : {}
173
+ ...metadata ? { metadata: { ...metadata } } : {}
108
174
  });
109
175
  var toAip193ErrorPayload = (error, fallbackDomain = "http-errors") => {
110
176
  const status = error.status ?? getCanonicalStatus(error.statusCode);
@@ -118,7 +184,7 @@ var toAip193ErrorPayload = (error, fallbackDomain = "http-errors") => {
118
184
  }
119
185
  };
120
186
  };
121
- var toRfc9457ErrorPayload = (error) => {
187
+ function toRfc9457ErrorPayload(error) {
122
188
  const errors = toRfc9457Errors(error.errors);
123
189
  return {
124
190
  type: error.type ?? RFC_9457_DEFAULT_TYPE,
@@ -128,67 +194,237 @@ var toRfc9457ErrorPayload = (error) => {
128
194
  ...error.instance ? { instance: error.instance } : {},
129
195
  ...errors ? { errors } : {}
130
196
  };
197
+ }
198
+ var toRfc9457ValidationErrorPayload = (error) => {
199
+ const errors = toRfc9457ValidationErrors(error.errors);
200
+ return {
201
+ type: error.type ?? RFC_9457_DEFAULT_TYPE,
202
+ title: error.title ?? getStatusTitle(error.statusCode),
203
+ status: error.statusCode,
204
+ detail: error.message,
205
+ ...error.instance ? { instance: error.instance } : {},
206
+ ...errors ? { errors } : {}
207
+ };
131
208
  };
132
- var toRfc9457ValidationErrorPayload = (error) => toRfc9457ErrorPayload(error);
133
209
 
134
210
  // src/base.ts
135
211
  var import_utils = require("@web-ts-toolkit/utils");
136
212
 
137
- // src/messages.ts
138
- var messages = {
139
- 400: "The server cannot process the request due to a client error",
140
- 401: "The user is not authorized",
141
- 403: "The server refused to authorize the request",
142
- 404: "The server did not find a current representation for the target resource",
143
- 405: "The method received is not allowed",
144
- 406: "The request is not acceptable to the user agent",
145
- 407: "The client needs to authenticate itself in order to use a proxy",
146
- 408: "The request was not completed in the expected time",
147
- 409: "The request was not completed due to a conflict with the target resource",
148
- 410: "The target resource is no longer available at the origin server",
149
- 411: "The server refuses to accept the request without a defined Content-Length",
150
- 412: "One or more conditions given in the request header fields evaluated to false",
151
- 413: "The request payload is too large",
152
- 414: "The request target is too long",
153
- 415: "The payload is in a format not supported",
154
- 416: "None of the ranges in the request's Range header field overlap the current extent of the selected resource",
155
- 417: "The expectation given in the request's Expect header field could not be met",
156
- 418: "I'm a teapot",
157
- 421: "The request was directed at a server that is not able to produce a response",
158
- 422: "The server is unable to process the request",
159
- 423: "The source or destination resource of a method is locked",
160
- 424: "The requested action depended on another action",
161
- 426: "This service requires use of a different protocol",
162
- 428: "This request is required to be conditional",
163
- 429: "The user has sent too many requests in a given amount of time",
164
- 431: "Request header fields too large",
165
- 451: "Denied access due to a consequence of a legal demand",
166
- 500: "The server encountered an unexpected condition",
167
- 501: "The server does not support the functionality required to fulfill the request",
168
- 502: "The server received an invalid response from an upstream server",
169
- 503: "The server is temporarily unable to handle the request",
170
- 504: "The server did not receive a timely response from an upstream server",
171
- 505: "The server does not support the HTTP protocol version used in the request",
172
- 506: "The server has an internal configuration error",
173
- 507: "The server is unable to store the representation needed to complete the request",
174
- 508: "The server detected an infinite loop while processing the request",
175
- 510: "Further extensions to the request are required for the server to fulfill it",
176
- 511: "The client needs to authenticate to gain network access"
213
+ // src/status-definitions.ts
214
+ var httpErrorStatusDefinitions = {
215
+ BadRequestError: {
216
+ statusCode: 400,
217
+ category: "client",
218
+ message: "The server cannot process the request due to a client error"
219
+ },
220
+ UnauthorizedError: {
221
+ statusCode: 401,
222
+ category: "client",
223
+ message: "The user is not authorized"
224
+ },
225
+ ForbiddenError: {
226
+ statusCode: 403,
227
+ category: "client",
228
+ message: "The server refused to authorize the request"
229
+ },
230
+ NotFoundError: {
231
+ statusCode: 404,
232
+ category: "client",
233
+ message: "The server did not find a current representation for the target resource"
234
+ },
235
+ MethodNotAllowedError: {
236
+ statusCode: 405,
237
+ category: "client",
238
+ message: "The method received is not allowed"
239
+ },
240
+ NotAcceptableError: {
241
+ statusCode: 406,
242
+ category: "client",
243
+ message: "The request is not acceptable to the user agent"
244
+ },
245
+ ProxyAuthRequiredError: {
246
+ statusCode: 407,
247
+ category: "client",
248
+ message: "The client needs to authenticate itself in order to use a proxy"
249
+ },
250
+ RequestTimeoutError: {
251
+ statusCode: 408,
252
+ category: "client",
253
+ message: "The request was not completed in the expected time"
254
+ },
255
+ ConflictError: {
256
+ statusCode: 409,
257
+ category: "client",
258
+ message: "The request was not completed due to a conflict with the target resource"
259
+ },
260
+ GoneError: {
261
+ statusCode: 410,
262
+ category: "client",
263
+ message: "The target resource is no longer available at the origin server"
264
+ },
265
+ LengthRequiredError: {
266
+ statusCode: 411,
267
+ category: "client",
268
+ message: "The server refuses to accept the request without a defined Content-Length"
269
+ },
270
+ PreconditionFailedError: {
271
+ statusCode: 412,
272
+ category: "client",
273
+ message: "One or more conditions given in the request header fields evaluated to false"
274
+ },
275
+ PayloadTooLargeError: {
276
+ statusCode: 413,
277
+ category: "client",
278
+ message: "The request payload is too large"
279
+ },
280
+ UriTooLongError: {
281
+ statusCode: 414,
282
+ category: "client",
283
+ message: "The request target is too long"
284
+ },
285
+ UnsupportedMediaTypeError: {
286
+ statusCode: 415,
287
+ category: "client",
288
+ message: "The payload is in a format not supported"
289
+ },
290
+ RequestedRangeNotSatisfiableError: {
291
+ statusCode: 416,
292
+ category: "client",
293
+ message: "None of the ranges in the request's Range header field overlap the current extent of the selected resource"
294
+ },
295
+ ExpectationFailedError: {
296
+ statusCode: 417,
297
+ category: "client",
298
+ message: "The expectation given in the request's Expect header field could not be met"
299
+ },
300
+ TeapotError: {
301
+ statusCode: 418,
302
+ category: "client",
303
+ message: "I'm a teapot"
304
+ },
305
+ MisdirectedRequestError: {
306
+ statusCode: 421,
307
+ category: "client",
308
+ message: "The request was directed at a server that is not able to produce a response"
309
+ },
310
+ UnprocessableEntityError: {
311
+ statusCode: 422,
312
+ category: "client",
313
+ message: "The server is unable to process the request"
314
+ },
315
+ LockedError: {
316
+ statusCode: 423,
317
+ category: "client",
318
+ message: "The source or destination resource of a method is locked"
319
+ },
320
+ FailedDependencyError: {
321
+ statusCode: 424,
322
+ category: "client",
323
+ message: "The requested action depended on another action"
324
+ },
325
+ UpgradeRequiredError: {
326
+ statusCode: 426,
327
+ category: "client",
328
+ message: "This service requires use of a different protocol"
329
+ },
330
+ PreconditionRequiredError: {
331
+ statusCode: 428,
332
+ category: "client",
333
+ message: "This request is required to be conditional"
334
+ },
335
+ TooManyRequestsError: {
336
+ statusCode: 429,
337
+ category: "client",
338
+ message: "The user has sent too many requests in a given amount of time"
339
+ },
340
+ RequestHeaderFieldsTooLargeError: {
341
+ statusCode: 431,
342
+ category: "client",
343
+ message: "Request header fields too large"
344
+ },
345
+ UnavailableForLegalReasonsError: {
346
+ statusCode: 451,
347
+ category: "client",
348
+ message: "Denied access due to a consequence of a legal demand"
349
+ },
350
+ InternalServerError: {
351
+ statusCode: 500,
352
+ category: "server",
353
+ message: "The server encountered an unexpected condition"
354
+ },
355
+ NotImplementedError: {
356
+ statusCode: 501,
357
+ category: "server",
358
+ message: "The server does not support the functionality required to fulfill the request"
359
+ },
360
+ BadGatewayError: {
361
+ statusCode: 502,
362
+ category: "server",
363
+ message: "The server received an invalid response from an upstream server"
364
+ },
365
+ ServiceUnavailableError: {
366
+ statusCode: 503,
367
+ category: "server",
368
+ message: "The server is temporarily unable to handle the request"
369
+ },
370
+ GatewayTimeoutError: {
371
+ statusCode: 504,
372
+ category: "server",
373
+ message: "The server did not receive a timely response from an upstream server"
374
+ },
375
+ HttpVersionNotSupportedError: {
376
+ statusCode: 505,
377
+ category: "server",
378
+ message: "The server does not support the HTTP protocol version used in the request"
379
+ },
380
+ VariantAlsoNegotiatesError: {
381
+ statusCode: 506,
382
+ category: "server",
383
+ message: "The server has an internal configuration error"
384
+ },
385
+ InsufficientStorageError: {
386
+ statusCode: 507,
387
+ category: "server",
388
+ message: "The server is unable to store the representation needed to complete the request"
389
+ },
390
+ LoopDetectedError: {
391
+ statusCode: 508,
392
+ category: "server",
393
+ message: "The server detected an infinite loop while processing the request"
394
+ },
395
+ NotExtendedError: {
396
+ statusCode: 510,
397
+ category: "server",
398
+ message: "Further extensions to the request are required for the server to fulfill it"
399
+ },
400
+ NetworkAuthenticationRequiredError: {
401
+ statusCode: 511,
402
+ category: "server",
403
+ message: "The client needs to authenticate to gain network access"
404
+ }
177
405
  };
406
+
407
+ // src/messages.ts
408
+ var messages = Object.freeze(
409
+ Object.fromEntries(
410
+ Object.values(httpErrorStatusDefinitions).map(({ statusCode, message }) => [statusCode, message])
411
+ )
412
+ );
178
413
  var getDefaultMessage = (statusCode) => messages[statusCode] || messages[500];
179
414
 
180
415
  // src/base.ts
181
416
  var ErrorCtor = Error;
182
417
  var HttpError = class extends Error {
183
418
  constructor(statusCode = 500, message, options = {}) {
184
- super(message ?? getDefaultMessage(statusCode), options);
419
+ const validatedStatusCode = validateHttpErrorStatusCode(statusCode);
420
+ super(message ?? getDefaultMessage(validatedStatusCode), options);
185
421
  const { status, reason, domain, metadata, details, errors, type, title, instance } = options;
186
422
  if (ErrorCtor.captureStackTrace) {
187
423
  ErrorCtor.captureStackTrace(this, this.constructor);
188
424
  }
189
425
  this.name = this.constructor.name;
190
- this.statusCode = statusCode;
191
- this.status = status ?? getCanonicalStatus(statusCode);
426
+ this.statusCode = validatedStatusCode;
427
+ this.status = status ?? getCanonicalStatus(validatedStatusCode);
192
428
  this.date = /* @__PURE__ */ new Date();
193
429
  if (reason !== void 0) {
194
430
  this.reason = reason;
@@ -198,13 +434,13 @@ var HttpError = class extends Error {
198
434
  }
199
435
  const normalizedMetadata = (0, import_utils.toStringRecord)(metadata);
200
436
  if (normalizedMetadata !== void 0) {
201
- this.metadata = normalizedMetadata;
437
+ this.metadata = Object.freeze(normalizedMetadata);
202
438
  }
203
439
  if (details !== void 0) {
204
- this.details = details;
440
+ this.details = Object.freeze([...details]);
205
441
  }
206
442
  if (errors !== void 0) {
207
- this.errors = errors;
443
+ this.errors = Array.isArray(errors) ? Object.freeze([...errors]) : errors;
208
444
  }
209
445
  if (type !== void 0) {
210
446
  this.type = type;
@@ -219,12 +455,12 @@ var HttpError = class extends Error {
219
455
  };
220
456
  var ClientError = class extends HttpError {
221
457
  constructor(statusCode = 400, message, options = {}) {
222
- super(statusCode, message, options);
458
+ super(validateClientErrorStatusCode(statusCode), message, options);
223
459
  }
224
460
  };
225
461
  var ServerError = class extends HttpError {
226
462
  constructor(statusCode = 500, message, options = {}) {
227
- super(statusCode, message, options);
463
+ super(validateServerErrorStatusCode(statusCode), message, options);
228
464
  }
229
465
  };
230
466
 
@@ -468,7 +704,11 @@ var NetworkAuthenticationRequiredError = class extends ServerError {
468
704
  createAip193ErrorInfoDetail,
469
705
  getCanonicalStatus,
470
706
  getStatusTitle,
707
+ isHttpErrorStatusCode,
471
708
  toAip193ErrorPayload,
472
709
  toRfc9457ErrorPayload,
473
- toRfc9457ValidationErrorPayload
710
+ toRfc9457ValidationErrorPayload,
711
+ validateClientErrorStatusCode,
712
+ validateHttpErrorStatusCode,
713
+ validateServerErrorStatusCode
474
714
  });