@web-ts-toolkit/http-errors 0.1.0 → 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 +1 -2
- package/README.md +51 -1
- package/index.d.mts +28 -3
- package/index.d.ts +28 -3
- package/index.js +61 -30
- package/index.mjs +57 -29
- package/package.json +5 -2
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
191
|
+
this.status = status ?? getCanonicalStatus(statusCode);
|
|
174
192
|
this.date = /* @__PURE__ */ new Date();
|
|
175
|
-
if (
|
|
176
|
-
this.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 (
|
|
179
|
-
this.
|
|
206
|
+
if (errors !== void 0) {
|
|
207
|
+
this.errors = errors;
|
|
180
208
|
}
|
|
181
|
-
if (
|
|
182
|
-
this.
|
|
209
|
+
if (type !== void 0) {
|
|
210
|
+
this.type = type;
|
|
183
211
|
}
|
|
184
|
-
if (
|
|
185
|
-
this.
|
|
212
|
+
if (title !== void 0) {
|
|
213
|
+
this.title = title;
|
|
186
214
|
}
|
|
187
|
-
if (
|
|
188
|
-
this.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
118
|
+
this.status = status ?? getCanonicalStatus(statusCode);
|
|
104
119
|
this.date = /* @__PURE__ */ new Date();
|
|
105
|
-
if (
|
|
106
|
-
this.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 (
|
|
109
|
-
this.
|
|
133
|
+
if (errors !== void 0) {
|
|
134
|
+
this.errors = errors;
|
|
110
135
|
}
|
|
111
|
-
if (
|
|
112
|
-
this.
|
|
136
|
+
if (type !== void 0) {
|
|
137
|
+
this.type = type;
|
|
113
138
|
}
|
|
114
|
-
if (
|
|
115
|
-
this.
|
|
139
|
+
if (title !== void 0) {
|
|
140
|
+
this.title = title;
|
|
116
141
|
}
|
|
117
|
-
if (
|
|
118
|
-
this.
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
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
|
},
|