Skip to content

Commit c1c3dd5

Browse files
authored
feat(event-handler): add error classes for http errors (aws-powertools#4299)
1 parent dc59488 commit c1c3dd5

File tree

4 files changed

+433
-1
lines changed

4 files changed

+433
-1
lines changed

packages/event-handler/src/rest/constants.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,80 @@ export const HttpVerbs = {
1010
OPTIONS: 'OPTIONS',
1111
} as const;
1212

13+
export const HttpErrorCodes = {
14+
// 1xx Informational
15+
CONTINUE: 100,
16+
SWITCHING_PROTOCOLS: 101,
17+
PROCESSING: 102,
18+
EARLY_HINTS: 103,
19+
20+
// 2xx Success
21+
OK: 200,
22+
CREATED: 201,
23+
ACCEPTED: 202,
24+
NON_AUTHORITATIVE_INFORMATION: 203,
25+
NO_CONTENT: 204,
26+
RESET_CONTENT: 205,
27+
PARTIAL_CONTENT: 206,
28+
MULTI_STATUS: 207,
29+
ALREADY_REPORTED: 208,
30+
IM_USED: 226,
31+
32+
// 3xx Redirection
33+
MULTIPLE_CHOICES: 300,
34+
MOVED_PERMANENTLY: 301,
35+
FOUND: 302,
36+
SEE_OTHER: 303,
37+
NOT_MODIFIED: 304,
38+
USE_PROXY: 305,
39+
TEMPORARY_REDIRECT: 307,
40+
PERMANENT_REDIRECT: 308,
41+
42+
// 4xx Client Error
43+
BAD_REQUEST: 400,
44+
UNAUTHORIZED: 401,
45+
PAYMENT_REQUIRED: 402,
46+
FORBIDDEN: 403,
47+
NOT_FOUND: 404,
48+
METHOD_NOT_ALLOWED: 405,
49+
NOT_ACCEPTABLE: 406,
50+
PROXY_AUTHENTICATION_REQUIRED: 407,
51+
REQUEST_TIMEOUT: 408,
52+
CONFLICT: 409,
53+
GONE: 410,
54+
LENGTH_REQUIRED: 411,
55+
PRECONDITION_FAILED: 412,
56+
REQUEST_ENTITY_TOO_LARGE: 413,
57+
REQUEST_URI_TOO_LONG: 414,
58+
UNSUPPORTED_MEDIA_TYPE: 415,
59+
REQUESTED_RANGE_NOT_SATISFIABLE: 416,
60+
EXPECTATION_FAILED: 417,
61+
IM_A_TEAPOT: 418,
62+
MISDIRECTED_REQUEST: 421,
63+
UNPROCESSABLE_ENTITY: 422,
64+
LOCKED: 423,
65+
FAILED_DEPENDENCY: 424,
66+
TOO_EARLY: 425,
67+
UPGRADE_REQUIRED: 426,
68+
PRECONDITION_REQUIRED: 428,
69+
TOO_MANY_REQUESTS: 429,
70+
REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
71+
UNAVAILABLE_FOR_LEGAL_REASONS: 451,
72+
73+
// 5xx Server Error
74+
INTERNAL_SERVER_ERROR: 500,
75+
NOT_IMPLEMENTED: 501,
76+
BAD_GATEWAY: 502,
77+
SERVICE_UNAVAILABLE: 503,
78+
GATEWAY_TIMEOUT: 504,
79+
HTTP_VERSION_NOT_SUPPORTED: 505,
80+
VARIANT_ALSO_NEGOTIATES: 506,
81+
INSUFFICIENT_STORAGE: 507,
82+
LOOP_DETECTED: 508,
83+
NOT_EXTENDED: 510,
84+
NETWORK_AUTHENTICATION_REQUIRED: 511,
85+
} as const;
86+
1387
export const PARAM_PATTERN = /:([a-zA-Z_]\w*)(?=\/|$)/g;
1488

1589
export const SAFE_CHARS = "-._~()'!*:@,;=+&$";

packages/event-handler/src/rest/errors.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import type { ErrorResponse, HttpStatusCode } from '../types/rest.js';
2+
import { HttpErrorCodes } from './constants.js';
3+
14
export class RouteMatchingError extends Error {
25
constructor(
36
message: string,
@@ -15,3 +18,145 @@ export class ParameterValidationError extends RouteMatchingError {
1518
this.name = 'ParameterValidationError';
1619
}
1720
}
21+
22+
abstract class ServiceError extends Error {
23+
abstract readonly statusCode: HttpStatusCode;
24+
abstract readonly errorType: string;
25+
public readonly details?: Record<string, unknown>;
26+
27+
constructor(
28+
message?: string,
29+
options?: ErrorOptions,
30+
details?: Record<string, unknown>
31+
) {
32+
super(message, options);
33+
this.name = 'ServiceError';
34+
this.details = details;
35+
}
36+
37+
toJSON(): ErrorResponse {
38+
return {
39+
statusCode: this.statusCode,
40+
error: this.errorType,
41+
message: this.message,
42+
...(this.details && { details: this.details }),
43+
};
44+
}
45+
}
46+
47+
export class BadRequestError extends ServiceError {
48+
readonly statusCode = HttpErrorCodes.BAD_REQUEST;
49+
readonly errorType = 'BadRequestError';
50+
51+
constructor(
52+
message?: string,
53+
options?: ErrorOptions,
54+
details?: Record<string, unknown>
55+
) {
56+
super(message, options, details);
57+
}
58+
}
59+
60+
export class UnauthorizedError extends ServiceError {
61+
readonly statusCode = HttpErrorCodes.UNAUTHORIZED;
62+
readonly errorType = 'UnauthorizedError';
63+
64+
constructor(
65+
message?: string,
66+
options?: ErrorOptions,
67+
details?: Record<string, unknown>
68+
) {
69+
super(message, options, details);
70+
}
71+
}
72+
73+
export class ForbiddenError extends ServiceError {
74+
readonly statusCode = HttpErrorCodes.FORBIDDEN;
75+
readonly errorType = 'ForbiddenError';
76+
77+
constructor(
78+
message?: string,
79+
options?: ErrorOptions,
80+
details?: Record<string, unknown>
81+
) {
82+
super(message, options, details);
83+
}
84+
}
85+
86+
export class NotFoundError extends ServiceError {
87+
readonly statusCode = HttpErrorCodes.NOT_FOUND;
88+
readonly errorType = 'NotFoundError';
89+
90+
constructor(
91+
message?: string,
92+
options?: ErrorOptions,
93+
details?: Record<string, unknown>
94+
) {
95+
super(message, options, details);
96+
}
97+
}
98+
99+
export class MethodNotAllowedError extends ServiceError {
100+
readonly statusCode = HttpErrorCodes.METHOD_NOT_ALLOWED;
101+
readonly errorType = 'MethodNotAllowedError';
102+
103+
constructor(
104+
message?: string,
105+
options?: ErrorOptions,
106+
details?: Record<string, unknown>
107+
) {
108+
super(message, options, details);
109+
}
110+
}
111+
112+
export class RequestTimeoutError extends ServiceError {
113+
readonly statusCode = HttpErrorCodes.REQUEST_TIMEOUT;
114+
readonly errorType = 'RequestTimeoutError';
115+
116+
constructor(
117+
message?: string,
118+
options?: ErrorOptions,
119+
details?: Record<string, unknown>
120+
) {
121+
super(message, options, details);
122+
}
123+
}
124+
125+
export class RequestEntityTooLargeError extends ServiceError {
126+
readonly statusCode = HttpErrorCodes.REQUEST_ENTITY_TOO_LARGE;
127+
readonly errorType = 'RequestEntityTooLargeError';
128+
129+
constructor(
130+
message?: string,
131+
options?: ErrorOptions,
132+
details?: Record<string, unknown>
133+
) {
134+
super(message, options, details);
135+
}
136+
}
137+
138+
export class InternalServerError extends ServiceError {
139+
readonly statusCode = HttpErrorCodes.INTERNAL_SERVER_ERROR;
140+
readonly errorType = 'InternalServerError';
141+
142+
constructor(
143+
message?: string,
144+
options?: ErrorOptions,
145+
details?: Record<string, unknown>
146+
) {
147+
super(message, options, details);
148+
}
149+
}
150+
151+
export class ServiceUnavailableError extends ServiceError {
152+
readonly statusCode = HttpErrorCodes.SERVICE_UNAVAILABLE;
153+
readonly errorType = 'ServiceUnavailableError';
154+
155+
constructor(
156+
message?: string,
157+
options?: ErrorOptions,
158+
details?: Record<string, unknown>
159+
) {
160+
super(message, options, details);
161+
}
162+
}

packages/event-handler/src/types/rest.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,32 @@
11
import type { GenericLogger } from '@aws-lambda-powertools/commons/types';
22
import type { BaseRouter } from '../rest/BaseRouter.js';
3-
import type { HttpVerbs } from '../rest/constants.js';
3+
import type { HttpErrorCodes, HttpVerbs } from '../rest/constants.js';
44
import type { Route } from '../rest/Route.js';
55

6+
type ErrorResponse = {
7+
statusCode: HttpStatusCode;
8+
error: string;
9+
message: string;
10+
};
11+
12+
interface ErrorContext {
13+
path: string;
14+
method: string;
15+
headers: Record<string, string>;
16+
timestamp: string;
17+
requestId?: string;
18+
}
19+
20+
type ErrorHandler<T extends Error = Error> = (
21+
error: T,
22+
context?: ErrorContext
23+
) => ErrorResponse;
24+
25+
interface ErrorConstructor<T extends Error = Error> {
26+
new (...args: any[]): T;
27+
prototype: T;
28+
}
29+
630
/**
731
* Options for the {@link BaseRouter} class
832
*/
@@ -29,6 +53,8 @@ type RouteHandler<T = any, R = any> = (...args: T[]) => R;
2953

3054
type HttpMethod = keyof typeof HttpVerbs;
3155

56+
type HttpStatusCode = (typeof HttpErrorCodes)[keyof typeof HttpErrorCodes];
57+
3258
type Path = `/${string}`;
3359

3460
type RouteHandlerOptions = {
@@ -59,6 +85,10 @@ type ValidationResult = {
5985
export type {
6086
CompiledRoute,
6187
DynamicRoute,
88+
ErrorResponse,
89+
ErrorConstructor,
90+
ErrorHandler,
91+
HttpStatusCode,
6292
HttpMethod,
6393
Path,
6494
RouterOptions,

0 commit comments

Comments
 (0)