@web-ts-toolkit/http-errors 0.0.2 → 0.2.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 CHANGED
@@ -1,4 +1,3 @@
1
-
2
1
  Apache License
3
2
  Version 2.0, January 2004
4
3
  http://www.apache.org/licenses/
@@ -187,7 +186,7 @@
187
186
  same "printed page" as the copyright notice for easier
188
187
  identification within third-party archives.
189
188
 
190
- Copyright © 2017, Province of British Columbia, Canada.
189
+ Copyright Junmin Ahn
191
190
 
192
191
  Licensed under the Apache License, Version 2.0 (the "License");
193
192
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -69,6 +69,9 @@ import { BadRequestError } from '@web-ts-toolkit/http-errors';
69
69
  throw new BadRequestError('invalid email', {
70
70
  reason: 'INVALID_EMAIL',
71
71
  domain: 'api.example.com',
72
+ type: 'https://api.example.com/problems/invalid-email',
73
+ title: 'Invalid email address',
74
+ instance: '/problems/invalid-email/123',
72
75
  metadata: {
73
76
  field: 'email',
74
77
  },
@@ -92,12 +95,15 @@ throw new BadRequestError('invalid email', {
92
95
  });
93
96
  ```
94
97
 
95
- The base `HttpError` now carries optional structured fields that are useful when building AIP-193-style error payloads:
98
+ The base `HttpError` now carries optional structured fields that are useful when building AIP-193 and RFC 9457 error payloads:
96
99
 
97
100
  - `statusCode`: HTTP status code
98
101
  - `status`: canonical status string for common HTTP codes, otherwise `UNKNOWN`
99
102
  - `reason`: application-specific machine-readable identifier
100
103
  - `domain`: logical error domain such as `api.example.com`
104
+ - `type`: RFC 9457 problem type URI
105
+ - `title`: RFC 9457 problem title
106
+ - `instance`: RFC 9457 problem instance URI
101
107
  - `metadata`: stringified key-value metadata
102
108
  - `details`: structured detail entries
103
109
  - `errors`: validation or field-level error payloads
@@ -118,6 +124,50 @@ const error = new BadRequestError('invalid email', {
118
124
  const payload = toAip193ErrorPayload(error);
119
125
  ```
120
126
 
127
+ ### Convert an error to an RFC 9457 payload
128
+
129
+ ```ts
130
+ import { BadRequestError, toRfc9457ErrorPayload } from '@web-ts-toolkit/http-errors';
131
+
132
+ const error = new BadRequestError('Email must be a valid address.', {
133
+ type: 'https://api.example.com/problems/invalid-email',
134
+ title: 'Invalid email address',
135
+ instance: '/problems/invalid-email/123',
136
+ errors: [
137
+ {
138
+ detail: 'must be a valid email address',
139
+ pointer: '#/email',
140
+ parameter: 'email',
141
+ },
142
+ {
143
+ detail: 'x-request-id header is required',
144
+ header: 'x-request-id',
145
+ },
146
+ ],
147
+ });
148
+
149
+ const payload = toRfc9457ErrorPayload(error);
150
+ ```
151
+
152
+ ### Use the typed RFC 9457 validation helper
153
+
154
+ ```ts
155
+ import { BadRequestError, toRfc9457ValidationErrorPayload } from '@web-ts-toolkit/http-errors';
156
+
157
+ const error = new BadRequestError('Email must be a valid address.', {
158
+ type: 'https://api.example.com/problems/invalid-email',
159
+ title: 'Invalid email address',
160
+ errors: [
161
+ {
162
+ detail: 'must be a valid email address',
163
+ pointer: '#/email',
164
+ },
165
+ ],
166
+ });
167
+
168
+ const payload = toRfc9457ValidationErrorPayload(error);
169
+ ```
170
+
121
171
  ### Express route example
122
172
 
123
173
  ```ts
package/index.d.mts CHANGED
@@ -1,5 +1,10 @@
1
1
  type HttpErrorMetadataValue = string | number | boolean | bigint | null | undefined;
2
- type HttpErrorOptions = ErrorOptions & {
2
+ type HttpErrorProblemFields = {
3
+ type?: string;
4
+ title?: string;
5
+ instance?: string;
6
+ };
7
+ type HttpErrorOptions = ErrorOptions & HttpErrorProblemFields & {
3
8
  status?: string;
4
9
  reason?: string;
5
10
  domain?: string;
@@ -7,7 +12,7 @@ type HttpErrorOptions = ErrorOptions & {
7
12
  details?: unknown[];
8
13
  errors?: unknown;
9
14
  };
10
- type HttpErrorShape = {
15
+ type HttpErrorShape = HttpErrorProblemFields & {
11
16
  statusCode: number;
12
17
  status?: string;
13
18
  message: string;
@@ -31,6 +36,20 @@ type Aip193ErrorPayload = {
31
36
  details?: unknown[];
32
37
  };
33
38
  };
39
+ type Rfc9457ErrorPayload<TError = unknown> = {
40
+ type?: string;
41
+ title?: string;
42
+ status?: number;
43
+ detail?: string;
44
+ instance?: string;
45
+ errors?: TError[];
46
+ };
47
+ type Rfc9457ValidationError = {
48
+ detail: string;
49
+ pointer?: string;
50
+ parameter?: string;
51
+ header?: string;
52
+ };
34
53
 
35
54
  declare const canonicalStatusByHttpStatus: {
36
55
  readonly 400: "INVALID_ARGUMENT";
@@ -46,9 +65,12 @@ declare const canonicalStatusByHttpStatus: {
46
65
  readonly 504: "DEADLINE_EXCEEDED";
47
66
  };
48
67
  declare const getCanonicalStatus: (statusCode: number) => string;
68
+ declare const getStatusTitle: (statusCode: number) => string;
49
69
 
50
70
  declare const createAip193ErrorInfoDetail: (reason: string, domain: string, metadata?: Record<string, string>) => Aip193ErrorInfoDetail;
51
71
  declare const toAip193ErrorPayload: (error: HttpErrorShape, fallbackDomain?: string) => Aip193ErrorPayload;
72
+ declare const toRfc9457ErrorPayload: <TError = unknown>(error: HttpErrorShape) => Rfc9457ErrorPayload<TError>;
73
+ declare const toRfc9457ValidationErrorPayload: (error: HttpErrorShape) => Rfc9457ErrorPayload<Rfc9457ValidationError>;
52
74
 
53
75
  declare class HttpError extends Error {
54
76
  readonly statusCode: number;
@@ -59,6 +81,9 @@ declare class HttpError extends Error {
59
81
  readonly metadata?: Record<string, string>;
60
82
  readonly details?: unknown[];
61
83
  readonly errors?: unknown;
84
+ readonly type?: string;
85
+ readonly title?: string;
86
+ readonly instance?: string;
62
87
  constructor(statusCode?: number, message?: string, options?: HttpErrorOptions);
63
88
  }
64
89
  declare class ClientError extends HttpError {
@@ -184,4 +209,4 @@ declare class NetworkAuthenticationRequiredError extends ServerError {
184
209
  constructor(message?: string, options?: HttpErrorOptions);
185
210
  }
186
211
 
187
- export { type Aip193ErrorInfoDetail, type Aip193ErrorPayload, BadGatewayError, BadRequestError, ClientError, ConflictError, ExpectationFailedError, FailedDependencyError, ForbiddenError, GatewayTimeoutError, GoneError, HttpError, type HttpErrorMetadataValue, type HttpErrorOptions, type HttpErrorShape, HttpVersionNotSupportedError, InsufficientStorageError, InternalServerError, LengthRequiredError, LockedError, LoopDetectedError, MethodNotAllowedError, MisdirectedRequestError, NetworkAuthenticationRequiredError, NotAcceptableError, NotExtendedError, NotFoundError, NotImplementedError, PayloadTooLargeError, PreconditionFailedError, PreconditionRequiredError, ProxyAuthRequiredError, RequestHeaderFieldsTooLargeError, RequestTimeoutError, RequestedRangeNotSatisfiableError, ServerError, ServiceUnavailableError, TeapotError, TooManyRequestsError, UnauthorizedError, UnavailableForLegalReasonsError, UnprocessableEntityError, UnsupportedMediaTypeError, UpgradeRequiredError, UriTooLongError, VariantAlsoNegotiatesError, canonicalStatusByHttpStatus, createAip193ErrorInfoDetail, getCanonicalStatus, toAip193ErrorPayload };
212
+ export { type Aip193ErrorInfoDetail, type Aip193ErrorPayload, BadGatewayError, BadRequestError, ClientError, ConflictError, ExpectationFailedError, FailedDependencyError, ForbiddenError, GatewayTimeoutError, GoneError, HttpError, type HttpErrorMetadataValue, type HttpErrorOptions, type HttpErrorProblemFields, type HttpErrorShape, HttpVersionNotSupportedError, InsufficientStorageError, InternalServerError, LengthRequiredError, LockedError, LoopDetectedError, MethodNotAllowedError, MisdirectedRequestError, NetworkAuthenticationRequiredError, NotAcceptableError, NotExtendedError, NotFoundError, NotImplementedError, PayloadTooLargeError, PreconditionFailedError, PreconditionRequiredError, ProxyAuthRequiredError, RequestHeaderFieldsTooLargeError, RequestTimeoutError, RequestedRangeNotSatisfiableError, type Rfc9457ErrorPayload, type Rfc9457ValidationError, ServerError, ServiceUnavailableError, TeapotError, TooManyRequestsError, UnauthorizedError, UnavailableForLegalReasonsError, UnprocessableEntityError, UnsupportedMediaTypeError, UpgradeRequiredError, UriTooLongError, VariantAlsoNegotiatesError, canonicalStatusByHttpStatus, createAip193ErrorInfoDetail, getCanonicalStatus, getStatusTitle, toAip193ErrorPayload, toRfc9457ErrorPayload, toRfc9457ValidationErrorPayload };
package/index.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  type HttpErrorMetadataValue = string | number | boolean | bigint | null | undefined;
2
- type HttpErrorOptions = ErrorOptions & {
2
+ type HttpErrorProblemFields = {
3
+ type?: string;
4
+ title?: string;
5
+ instance?: string;
6
+ };
7
+ type HttpErrorOptions = ErrorOptions & HttpErrorProblemFields & {
3
8
  status?: string;
4
9
  reason?: string;
5
10
  domain?: string;
@@ -7,7 +12,7 @@ type HttpErrorOptions = ErrorOptions & {
7
12
  details?: unknown[];
8
13
  errors?: unknown;
9
14
  };
10
- type HttpErrorShape = {
15
+ type HttpErrorShape = HttpErrorProblemFields & {
11
16
  statusCode: number;
12
17
  status?: string;
13
18
  message: string;
@@ -31,6 +36,20 @@ type Aip193ErrorPayload = {
31
36
  details?: unknown[];
32
37
  };
33
38
  };
39
+ type Rfc9457ErrorPayload<TError = unknown> = {
40
+ type?: string;
41
+ title?: string;
42
+ status?: number;
43
+ detail?: string;
44
+ instance?: string;
45
+ errors?: TError[];
46
+ };
47
+ type Rfc9457ValidationError = {
48
+ detail: string;
49
+ pointer?: string;
50
+ parameter?: string;
51
+ header?: string;
52
+ };
34
53
 
35
54
  declare const canonicalStatusByHttpStatus: {
36
55
  readonly 400: "INVALID_ARGUMENT";
@@ -46,9 +65,12 @@ declare const canonicalStatusByHttpStatus: {
46
65
  readonly 504: "DEADLINE_EXCEEDED";
47
66
  };
48
67
  declare const getCanonicalStatus: (statusCode: number) => string;
68
+ declare const getStatusTitle: (statusCode: number) => string;
49
69
 
50
70
  declare const createAip193ErrorInfoDetail: (reason: string, domain: string, metadata?: Record<string, string>) => Aip193ErrorInfoDetail;
51
71
  declare const toAip193ErrorPayload: (error: HttpErrorShape, fallbackDomain?: string) => Aip193ErrorPayload;
72
+ declare const toRfc9457ErrorPayload: <TError = unknown>(error: HttpErrorShape) => Rfc9457ErrorPayload<TError>;
73
+ declare const toRfc9457ValidationErrorPayload: (error: HttpErrorShape) => Rfc9457ErrorPayload<Rfc9457ValidationError>;
52
74
 
53
75
  declare class HttpError extends Error {
54
76
  readonly statusCode: number;
@@ -59,6 +81,9 @@ declare class HttpError extends Error {
59
81
  readonly metadata?: Record<string, string>;
60
82
  readonly details?: unknown[];
61
83
  readonly errors?: unknown;
84
+ readonly type?: string;
85
+ readonly title?: string;
86
+ readonly instance?: string;
62
87
  constructor(statusCode?: number, message?: string, options?: HttpErrorOptions);
63
88
  }
64
89
  declare class ClientError extends HttpError {
@@ -184,4 +209,4 @@ declare class NetworkAuthenticationRequiredError extends ServerError {
184
209
  constructor(message?: string, options?: HttpErrorOptions);
185
210
  }
186
211
 
187
- export { type Aip193ErrorInfoDetail, type Aip193ErrorPayload, BadGatewayError, BadRequestError, ClientError, ConflictError, ExpectationFailedError, FailedDependencyError, ForbiddenError, GatewayTimeoutError, GoneError, HttpError, type HttpErrorMetadataValue, type HttpErrorOptions, type HttpErrorShape, HttpVersionNotSupportedError, InsufficientStorageError, InternalServerError, LengthRequiredError, LockedError, LoopDetectedError, MethodNotAllowedError, MisdirectedRequestError, NetworkAuthenticationRequiredError, NotAcceptableError, NotExtendedError, NotFoundError, NotImplementedError, PayloadTooLargeError, PreconditionFailedError, PreconditionRequiredError, ProxyAuthRequiredError, RequestHeaderFieldsTooLargeError, RequestTimeoutError, RequestedRangeNotSatisfiableError, ServerError, ServiceUnavailableError, TeapotError, TooManyRequestsError, UnauthorizedError, UnavailableForLegalReasonsError, UnprocessableEntityError, UnsupportedMediaTypeError, UpgradeRequiredError, UriTooLongError, VariantAlsoNegotiatesError, canonicalStatusByHttpStatus, createAip193ErrorInfoDetail, getCanonicalStatus, toAip193ErrorPayload };
212
+ export { type Aip193ErrorInfoDetail, type Aip193ErrorPayload, BadGatewayError, BadRequestError, ClientError, ConflictError, ExpectationFailedError, FailedDependencyError, ForbiddenError, GatewayTimeoutError, GoneError, HttpError, type HttpErrorMetadataValue, type HttpErrorOptions, type HttpErrorProblemFields, type HttpErrorShape, HttpVersionNotSupportedError, InsufficientStorageError, InternalServerError, LengthRequiredError, LockedError, LoopDetectedError, MethodNotAllowedError, MisdirectedRequestError, NetworkAuthenticationRequiredError, NotAcceptableError, NotExtendedError, NotFoundError, NotImplementedError, PayloadTooLargeError, PreconditionFailedError, PreconditionRequiredError, ProxyAuthRequiredError, RequestHeaderFieldsTooLargeError, RequestTimeoutError, RequestedRangeNotSatisfiableError, type Rfc9457ErrorPayload, type Rfc9457ValidationError, ServerError, ServiceUnavailableError, TeapotError, TooManyRequestsError, UnauthorizedError, UnavailableForLegalReasonsError, UnprocessableEntityError, UnsupportedMediaTypeError, UpgradeRequiredError, UriTooLongError, VariantAlsoNegotiatesError, canonicalStatusByHttpStatus, createAip193ErrorInfoDetail, getCanonicalStatus, getStatusTitle, toAip193ErrorPayload, toRfc9457ErrorPayload, toRfc9457ValidationErrorPayload };
package/index.js CHANGED
@@ -64,11 +64,15 @@ __export(index_exports, {
64
64
  canonicalStatusByHttpStatus: () => canonicalStatusByHttpStatus,
65
65
  createAip193ErrorInfoDetail: () => createAip193ErrorInfoDetail,
66
66
  getCanonicalStatus: () => getCanonicalStatus,
67
- toAip193ErrorPayload: () => toAip193ErrorPayload
67
+ getStatusTitle: () => getStatusTitle,
68
+ toAip193ErrorPayload: () => toAip193ErrorPayload,
69
+ toRfc9457ErrorPayload: () => toRfc9457ErrorPayload,
70
+ toRfc9457ValidationErrorPayload: () => toRfc9457ValidationErrorPayload
68
71
  });
69
72
  module.exports = __toCommonJS(index_exports);
70
73
 
71
74
  // src/status.ts
75
+ var import_node_http = require("http");
72
76
  var canonicalStatusByHttpStatus = {
73
77
  400: "INVALID_ARGUMENT",
74
78
  401: "UNAUTHENTICATED",
@@ -83,8 +87,19 @@ var canonicalStatusByHttpStatus = {
83
87
  504: "DEADLINE_EXCEEDED"
84
88
  };
85
89
  var getCanonicalStatus = (statusCode) => canonicalStatusByHttpStatus[statusCode] || "UNKNOWN";
90
+ var getStatusTitle = (statusCode) => import_node_http.STATUS_CODES[statusCode] || "Unknown Error";
86
91
 
87
92
  // src/serialize.ts
93
+ var RFC_9457_DEFAULT_TYPE = "about:blank";
94
+ var toAip193ErrorDetails = (error, fallbackDomain) => {
95
+ const status = error.status ?? getCanonicalStatus(error.statusCode);
96
+ return [
97
+ createAip193ErrorInfoDetail(error.reason ?? status, error.domain ?? fallbackDomain, error.metadata),
98
+ ...error.errors !== void 0 ? [{ type: "bad_request", errors: error.errors }] : [],
99
+ ...error.details ?? []
100
+ ];
101
+ };
102
+ var toRfc9457Errors = (errors) => Array.isArray(errors) ? errors : void 0;
88
103
  var createAip193ErrorInfoDetail = (reason, domain, metadata) => ({
89
104
  type: "error_info",
90
105
  reason,
@@ -92,21 +107,32 @@ var createAip193ErrorInfoDetail = (reason, domain, metadata) => ({
92
107
  ...metadata ? { metadata } : {}
93
108
  });
94
109
  var toAip193ErrorPayload = (error, fallbackDomain = "http-errors") => {
95
- const status = error.status || getCanonicalStatus(error.statusCode);
96
- const details = [
97
- createAip193ErrorInfoDetail(error.reason || status, error.domain || fallbackDomain, error.metadata),
98
- ...error.errors !== void 0 ? [{ type: "bad_request", errors: error.errors }] : [],
99
- ...error.details || []
100
- ];
110
+ const status = error.status ?? getCanonicalStatus(error.statusCode);
111
+ const details = toAip193ErrorDetails(error, fallbackDomain);
101
112
  return {
102
113
  error: {
103
114
  code: error.statusCode,
104
115
  status,
105
116
  message: error.message,
106
- ...details.length > 0 ? { details } : {}
117
+ details
107
118
  }
108
119
  };
109
120
  };
121
+ var toRfc9457ErrorPayload = (error) => {
122
+ const errors = toRfc9457Errors(error.errors);
123
+ return {
124
+ type: error.type ?? RFC_9457_DEFAULT_TYPE,
125
+ title: error.title ?? getStatusTitle(error.statusCode),
126
+ status: error.statusCode,
127
+ detail: error.message,
128
+ ...error.instance ? { instance: error.instance } : {},
129
+ ...errors ? { errors } : {}
130
+ };
131
+ };
132
+ var toRfc9457ValidationErrorPayload = (error) => toRfc9457ErrorPayload(error);
133
+
134
+ // src/base.ts
135
+ var import_utils = require("@web-ts-toolkit/utils");
110
136
 
111
137
  // src/messages.ts
112
138
  var messages = {
@@ -153,39 +179,41 @@ var getDefaultMessage = (statusCode) => messages[statusCode] || messages[500];
153
179
 
154
180
  // src/base.ts
155
181
  var ErrorCtor = Error;
156
- var toMetadata = (metadata) => {
157
- if (!metadata) {
158
- return void 0;
159
- }
160
- return Object.entries(metadata).reduce((result, [key, value]) => {
161
- result[key] = String(value);
162
- return result;
163
- }, {});
164
- };
165
182
  var HttpError = class extends Error {
166
183
  constructor(statusCode = 500, message, options = {}) {
167
- super(message || getDefaultMessage(statusCode), options);
184
+ super(message ?? getDefaultMessage(statusCode), options);
185
+ const { status, reason, domain, metadata, details, errors, type, title, instance } = options;
168
186
  if (ErrorCtor.captureStackTrace) {
169
187
  ErrorCtor.captureStackTrace(this, this.constructor);
170
188
  }
171
189
  this.name = this.constructor.name;
172
190
  this.statusCode = statusCode;
173
- this.status = options.status || getCanonicalStatus(statusCode);
191
+ this.status = status ?? getCanonicalStatus(statusCode);
174
192
  this.date = /* @__PURE__ */ new Date();
175
- if (options.reason) {
176
- this.reason = options.reason;
193
+ if (reason !== void 0) {
194
+ this.reason = reason;
195
+ }
196
+ if (domain !== void 0) {
197
+ this.domain = domain;
198
+ }
199
+ const normalizedMetadata = (0, import_utils.toStringRecord)(metadata);
200
+ if (normalizedMetadata !== void 0) {
201
+ this.metadata = normalizedMetadata;
202
+ }
203
+ if (details !== void 0) {
204
+ this.details = details;
177
205
  }
178
- if (options.domain) {
179
- this.domain = options.domain;
206
+ if (errors !== void 0) {
207
+ this.errors = errors;
180
208
  }
181
- if (options.metadata) {
182
- this.metadata = toMetadata(options.metadata);
209
+ if (type !== void 0) {
210
+ this.type = type;
183
211
  }
184
- if (options.details) {
185
- this.details = options.details;
212
+ if (title !== void 0) {
213
+ this.title = title;
186
214
  }
187
- if (options.errors !== void 0) {
188
- this.errors = options.errors;
215
+ if (instance !== void 0) {
216
+ this.instance = instance;
189
217
  }
190
218
  }
191
219
  };
@@ -439,5 +467,8 @@ var NetworkAuthenticationRequiredError = class extends ServerError {
439
467
  canonicalStatusByHttpStatus,
440
468
  createAip193ErrorInfoDetail,
441
469
  getCanonicalStatus,
442
- toAip193ErrorPayload
470
+ getStatusTitle,
471
+ toAip193ErrorPayload,
472
+ toRfc9457ErrorPayload,
473
+ toRfc9457ValidationErrorPayload
443
474
  });
package/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/status.ts
2
+ import { STATUS_CODES } from "http";
2
3
  var canonicalStatusByHttpStatus = {
3
4
  400: "INVALID_ARGUMENT",
4
5
  401: "UNAUTHENTICATED",
@@ -13,8 +14,19 @@ var canonicalStatusByHttpStatus = {
13
14
  504: "DEADLINE_EXCEEDED"
14
15
  };
15
16
  var getCanonicalStatus = (statusCode) => canonicalStatusByHttpStatus[statusCode] || "UNKNOWN";
17
+ var getStatusTitle = (statusCode) => STATUS_CODES[statusCode] || "Unknown Error";
16
18
 
17
19
  // src/serialize.ts
20
+ var RFC_9457_DEFAULT_TYPE = "about:blank";
21
+ var toAip193ErrorDetails = (error, fallbackDomain) => {
22
+ const status = error.status ?? getCanonicalStatus(error.statusCode);
23
+ return [
24
+ createAip193ErrorInfoDetail(error.reason ?? status, error.domain ?? fallbackDomain, error.metadata),
25
+ ...error.errors !== void 0 ? [{ type: "bad_request", errors: error.errors }] : [],
26
+ ...error.details ?? []
27
+ ];
28
+ };
29
+ var toRfc9457Errors = (errors) => Array.isArray(errors) ? errors : void 0;
18
30
  var createAip193ErrorInfoDetail = (reason, domain, metadata) => ({
19
31
  type: "error_info",
20
32
  reason,
@@ -22,21 +34,32 @@ var createAip193ErrorInfoDetail = (reason, domain, metadata) => ({
22
34
  ...metadata ? { metadata } : {}
23
35
  });
24
36
  var toAip193ErrorPayload = (error, fallbackDomain = "http-errors") => {
25
- const status = error.status || getCanonicalStatus(error.statusCode);
26
- const details = [
27
- createAip193ErrorInfoDetail(error.reason || status, error.domain || fallbackDomain, error.metadata),
28
- ...error.errors !== void 0 ? [{ type: "bad_request", errors: error.errors }] : [],
29
- ...error.details || []
30
- ];
37
+ const status = error.status ?? getCanonicalStatus(error.statusCode);
38
+ const details = toAip193ErrorDetails(error, fallbackDomain);
31
39
  return {
32
40
  error: {
33
41
  code: error.statusCode,
34
42
  status,
35
43
  message: error.message,
36
- ...details.length > 0 ? { details } : {}
44
+ details
37
45
  }
38
46
  };
39
47
  };
48
+ var toRfc9457ErrorPayload = (error) => {
49
+ const errors = toRfc9457Errors(error.errors);
50
+ return {
51
+ type: error.type ?? RFC_9457_DEFAULT_TYPE,
52
+ title: error.title ?? getStatusTitle(error.statusCode),
53
+ status: error.statusCode,
54
+ detail: error.message,
55
+ ...error.instance ? { instance: error.instance } : {},
56
+ ...errors ? { errors } : {}
57
+ };
58
+ };
59
+ var toRfc9457ValidationErrorPayload = (error) => toRfc9457ErrorPayload(error);
60
+
61
+ // src/base.ts
62
+ import { toStringRecord } from "@web-ts-toolkit/utils";
40
63
 
41
64
  // src/messages.ts
42
65
  var messages = {
@@ -83,39 +106,41 @@ var getDefaultMessage = (statusCode) => messages[statusCode] || messages[500];
83
106
 
84
107
  // src/base.ts
85
108
  var ErrorCtor = Error;
86
- var toMetadata = (metadata) => {
87
- if (!metadata) {
88
- return void 0;
89
- }
90
- return Object.entries(metadata).reduce((result, [key, value]) => {
91
- result[key] = String(value);
92
- return result;
93
- }, {});
94
- };
95
109
  var HttpError = class extends Error {
96
110
  constructor(statusCode = 500, message, options = {}) {
97
- super(message || getDefaultMessage(statusCode), options);
111
+ super(message ?? getDefaultMessage(statusCode), options);
112
+ const { status, reason, domain, metadata, details, errors, type, title, instance } = options;
98
113
  if (ErrorCtor.captureStackTrace) {
99
114
  ErrorCtor.captureStackTrace(this, this.constructor);
100
115
  }
101
116
  this.name = this.constructor.name;
102
117
  this.statusCode = statusCode;
103
- this.status = options.status || getCanonicalStatus(statusCode);
118
+ this.status = status ?? getCanonicalStatus(statusCode);
104
119
  this.date = /* @__PURE__ */ new Date();
105
- if (options.reason) {
106
- this.reason = options.reason;
120
+ if (reason !== void 0) {
121
+ this.reason = reason;
122
+ }
123
+ if (domain !== void 0) {
124
+ this.domain = domain;
125
+ }
126
+ const normalizedMetadata = toStringRecord(metadata);
127
+ if (normalizedMetadata !== void 0) {
128
+ this.metadata = normalizedMetadata;
129
+ }
130
+ if (details !== void 0) {
131
+ this.details = details;
107
132
  }
108
- if (options.domain) {
109
- this.domain = options.domain;
133
+ if (errors !== void 0) {
134
+ this.errors = errors;
110
135
  }
111
- if (options.metadata) {
112
- this.metadata = toMetadata(options.metadata);
136
+ if (type !== void 0) {
137
+ this.type = type;
113
138
  }
114
- if (options.details) {
115
- this.details = options.details;
139
+ if (title !== void 0) {
140
+ this.title = title;
116
141
  }
117
- if (options.errors !== void 0) {
118
- this.errors = options.errors;
142
+ if (instance !== void 0) {
143
+ this.instance = instance;
119
144
  }
120
145
  }
121
146
  };
@@ -368,5 +393,8 @@ export {
368
393
  canonicalStatusByHttpStatus,
369
394
  createAip193ErrorInfoDetail,
370
395
  getCanonicalStatus,
371
- toAip193ErrorPayload
396
+ getStatusTitle,
397
+ toAip193ErrorPayload,
398
+ toRfc9457ErrorPayload,
399
+ toRfc9457ValidationErrorPayload
372
400
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@web-ts-toolkit/http-errors",
3
3
  "description": "HTTP error classes for backend APIs",
4
- "version": "0.0.2",
4
+ "version": "0.2.0",
5
5
  "keywords": [
6
6
  "http-errors",
7
7
  "api-errors"
@@ -17,7 +17,10 @@
17
17
  "default": "./index.js"
18
18
  }
19
19
  },
20
- "author": "Junmin Dev",
20
+ "dependencies": {
21
+ "@web-ts-toolkit/utils": "0.2.0"
22
+ },
23
+ "author": "Junmin Ahn",
21
24
  "bugs": {
22
25
  "url": "https://github.com/egose/web-ts-toolkit/issues"
23
26
  },