Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ declare function getTodoById<T>(todoId: unknown): Promise<{ id: string } & T>;
declare class GetTodoError extends Error {}

import {
HttpErrorCodes,
HttpStatusCodes,
Router,
} from '@aws-lambda-powertools/event-handler/experimental-rest';
import { Logger } from '@aws-lambda-powertools/logger';
Expand All @@ -15,7 +15,7 @@ app.errorHandler(GetTodoError, async (error, reqCtx) => {
logger.error('Unable to get todo', { error });

return {
statusCode: HttpErrorCodes.BAD_REQUEST,
statusCode: HttpStatusCodes.BAD_REQUEST,
message: `Bad request: ${error.message} - ${reqCtx.request.headers.get('x-correlation-id')}`,
error: 'BadRequest',
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
HttpErrorCodes,
HttpStatusCodes,
Router,
} from '@aws-lambda-powertools/event-handler/experimental-rest';
import { Logger } from '@aws-lambda-powertools/logger';
Expand All @@ -12,7 +12,7 @@ app.notFound(async (error, reqCtx) => {
logger.error('Unable to get todo', { error });

return {
statusCode: HttpErrorCodes.IM_A_TEAPOT,
statusCode: HttpStatusCodes.IM_A_TEAPOT,
body: "I'm a teapot!",
headers: {
'x-correlation-id': reqCtx.request.headers.get('x-correlation-id'),
Expand Down
11 changes: 6 additions & 5 deletions packages/event-handler/src/rest/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type {
RestRouterOptions,
RouteHandler,
} from '../types/rest.js';
import { HttpErrorCodes, HttpVerbs } from './constants.js';
import { HttpStatusCodes, HttpVerbs } from './constants.js';
import {
handlerResultToProxyResult,
handlerResultToWebResponse,
Expand Down Expand Up @@ -214,7 +214,7 @@ class Router {
// We can't throw a MethodNotAllowedError outside the try block as it
// will be converted to an internal server error by the API Gateway runtime
return {
statusCode: HttpErrorCodes.METHOD_NOT_ALLOWED,
statusCode: HttpStatusCodes.METHOD_NOT_ALLOWED,
body: '',
};
}
Expand Down Expand Up @@ -327,14 +327,15 @@ class Router {
}
if (!body.statusCode) {
if (error instanceof NotFoundError) {
body.statusCode = HttpErrorCodes.NOT_FOUND;
body.statusCode = HttpStatusCodes.NOT_FOUND;
} else if (error instanceof MethodNotAllowedError) {
body.statusCode = HttpErrorCodes.METHOD_NOT_ALLOWED;
body.statusCode = HttpStatusCodes.METHOD_NOT_ALLOWED;
}
}
return new Response(JSON.stringify(body), {
status:
(body.statusCode as number) ?? HttpErrorCodes.INTERNAL_SERVER_ERROR,
(body.statusCode as number) ??
HttpStatusCodes.INTERNAL_SERVER_ERROR,
headers: { 'Content-Type': 'application/json' },
});
} catch (handlerError) {
Expand Down
33 changes: 22 additions & 11 deletions packages/event-handler/src/rest/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const HttpVerbs = {
const HttpVerbs = {
GET: 'GET',
POST: 'POST',
PUT: 'PUT',
Expand All @@ -8,7 +8,7 @@ export const HttpVerbs = {
OPTIONS: 'OPTIONS',
} as const;

export const HttpErrorCodes = {
const HttpStatusCodes = {
// 1xx Informational
CONTINUE: 100,
SWITCHING_PROTOCOLS: 101,
Expand Down Expand Up @@ -82,16 +82,16 @@ export const HttpErrorCodes = {
NETWORK_AUTHENTICATION_REQUIRED: 511,
} as const;

export const PARAM_PATTERN = /:([a-zA-Z_]\w*)(?=\/|$)/g;
const PARAM_PATTERN = /:([a-zA-Z_]\w*)(?=\/|$)/g;

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

export const UNSAFE_CHARS = '%<> \\[\\]{}|^';
const UNSAFE_CHARS = '%<> \\[\\]{}|^';

/**
* Default CORS configuration
*/
export const DEFAULT_CORS_OPTIONS = {
const DEFAULT_CORS_OPTIONS = {
origin: '*',
allowMethods: ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'],
allowHeaders: [
Expand All @@ -102,17 +102,28 @@ export const DEFAULT_CORS_OPTIONS = {
'X-Amz-Security-Token',
],
exposeHeaders: [],
credentials: false
credentials: false,
};

export const DEFAULT_COMPRESSION_RESPONSE_THRESHOLD = 1024;
const DEFAULT_COMPRESSION_RESPONSE_THRESHOLD = 1024;

export const CACHE_CONTROL_NO_TRANSFORM_REGEX =
/(?:^|,)\s*?no-transform\s*?(?:,|$)/i;
const CACHE_CONTROL_NO_TRANSFORM_REGEX = /(?:^|,)\s*?no-transform\s*?(?:,|$)/i;

export const COMPRESSION_ENCODING_TYPES = {
const COMPRESSION_ENCODING_TYPES = {
GZIP: 'gzip',
DEFLATE: 'deflate',
IDENTITY: 'identity',
ANY: '*',
} as const;

export {
HttpVerbs,
HttpStatusCodes,
PARAM_PATTERN,
SAFE_CHARS,
UNSAFE_CHARS,
DEFAULT_CORS_OPTIONS,
DEFAULT_COMPRESSION_RESPONSE_THRESHOLD,
CACHE_CONTROL_NO_TRANSFORM_REGEX,
COMPRESSION_ENCODING_TYPES,
};
4 changes: 2 additions & 2 deletions packages/event-handler/src/rest/converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {
HandlerResponse,
HttpStatusCode,
} from '../types/rest.js';
import { COMPRESSION_ENCODING_TYPES, HttpErrorCodes } from './constants.js';
import { COMPRESSION_ENCODING_TYPES, HttpStatusCodes } from './constants.js';
import { isAPIGatewayProxyResult } from './utils.js';

/**
Expand Down Expand Up @@ -190,7 +190,7 @@ export const handlerResultToWebResponse = (
*/
export const handlerResultToProxyResult = async (
response: HandlerResponse,
statusCode: HttpStatusCode = HttpErrorCodes.OK
statusCode: HttpStatusCode = HttpStatusCodes.OK
): Promise<APIGatewayProxyResult> => {
if (isAPIGatewayProxyResult(response)) {
return response;
Expand Down
68 changes: 46 additions & 22 deletions packages/event-handler/src/rest/errors.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { JSONValue } from '@aws-lambda-powertools/commons/types';
import type { HandlerResponse, HttpStatusCode } from '../types/rest.js';
import { HttpErrorCodes } from './constants.js';
import { HttpStatusCodes } from './constants.js';

export class RouteMatchingError extends Error {
class RouteMatchingError extends Error {
constructor(
message: string,
public readonly path: string,
Expand All @@ -13,14 +13,14 @@ export class RouteMatchingError extends Error {
}
}

export class ParameterValidationError extends RouteMatchingError {
class ParameterValidationError extends RouteMatchingError {
constructor(public readonly issues: string[]) {
super(`Parameter validation failed: ${issues.join(', ')}`, '', '');
this.name = 'ParameterValidationError';
}
}

export abstract class ServiceError extends Error {
abstract class ServiceError extends Error {
abstract readonly statusCode: HttpStatusCode;
abstract readonly errorType: string;
public readonly details?: Record<string, unknown>;
Expand All @@ -47,8 +47,8 @@ export abstract class ServiceError extends Error {
}
}

export class BadRequestError extends ServiceError {
readonly statusCode = HttpErrorCodes.BAD_REQUEST;
class BadRequestError extends ServiceError {
readonly statusCode = HttpStatusCodes.BAD_REQUEST;
readonly errorType = 'BadRequestError';

constructor(
Expand All @@ -57,11 +57,12 @@ export class BadRequestError extends ServiceError {
details?: Record<string, unknown>
) {
super(message, options, details);
this.name = 'BadRequestError';
}
}

export class UnauthorizedError extends ServiceError {
readonly statusCode = HttpErrorCodes.UNAUTHORIZED;
class UnauthorizedError extends ServiceError {
readonly statusCode = HttpStatusCodes.UNAUTHORIZED;
readonly errorType = 'UnauthorizedError';

constructor(
Expand All @@ -70,11 +71,12 @@ export class UnauthorizedError extends ServiceError {
details?: Record<string, unknown>
) {
super(message, options, details);
this.name = 'UnauthorizedError';
}
}

export class ForbiddenError extends ServiceError {
readonly statusCode = HttpErrorCodes.FORBIDDEN;
class ForbiddenError extends ServiceError {
readonly statusCode = HttpStatusCodes.FORBIDDEN;
readonly errorType = 'ForbiddenError';

constructor(
Expand All @@ -83,11 +85,12 @@ export class ForbiddenError extends ServiceError {
details?: Record<string, unknown>
) {
super(message, options, details);
this.name = 'ForbiddenError';
}
}

export class NotFoundError extends ServiceError {
readonly statusCode = HttpErrorCodes.NOT_FOUND;
class NotFoundError extends ServiceError {
readonly statusCode = HttpStatusCodes.NOT_FOUND;
readonly errorType = 'NotFoundError';

constructor(
Expand All @@ -96,11 +99,12 @@ export class NotFoundError extends ServiceError {
details?: Record<string, unknown>
) {
super(message, options, details);
this.name = 'NotFoundError';
}
}

export class MethodNotAllowedError extends ServiceError {
readonly statusCode = HttpErrorCodes.METHOD_NOT_ALLOWED;
class MethodNotAllowedError extends ServiceError {
readonly statusCode = HttpStatusCodes.METHOD_NOT_ALLOWED;
readonly errorType = 'MethodNotAllowedError';

constructor(
Expand All @@ -109,11 +113,12 @@ export class MethodNotAllowedError extends ServiceError {
details?: Record<string, unknown>
) {
super(message, options, details);
this.name = 'MethodNotAllowedError';
}
}

export class RequestTimeoutError extends ServiceError {
readonly statusCode = HttpErrorCodes.REQUEST_TIMEOUT;
class RequestTimeoutError extends ServiceError {
readonly statusCode = HttpStatusCodes.REQUEST_TIMEOUT;
readonly errorType = 'RequestTimeoutError';

constructor(
Expand All @@ -122,11 +127,12 @@ export class RequestTimeoutError extends ServiceError {
details?: Record<string, unknown>
) {
super(message, options, details);
this.name = 'RequestTimeoutError';
}
}

export class RequestEntityTooLargeError extends ServiceError {
readonly statusCode = HttpErrorCodes.REQUEST_ENTITY_TOO_LARGE;
class RequestEntityTooLargeError extends ServiceError {
readonly statusCode = HttpStatusCodes.REQUEST_ENTITY_TOO_LARGE;
readonly errorType = 'RequestEntityTooLargeError';

constructor(
Expand All @@ -135,11 +141,12 @@ export class RequestEntityTooLargeError extends ServiceError {
details?: Record<string, unknown>
) {
super(message, options, details);
this.name = 'RequestEntityTooLargeError';
}
}

export class InternalServerError extends ServiceError {
readonly statusCode = HttpErrorCodes.INTERNAL_SERVER_ERROR;
class InternalServerError extends ServiceError {
readonly statusCode = HttpStatusCodes.INTERNAL_SERVER_ERROR;
readonly errorType = 'InternalServerError';

constructor(
Expand All @@ -148,11 +155,12 @@ export class InternalServerError extends ServiceError {
details?: Record<string, unknown>
) {
super(message, options, details);
this.name = 'InternalServerError';
}
}

export class ServiceUnavailableError extends ServiceError {
readonly statusCode = HttpErrorCodes.SERVICE_UNAVAILABLE;
class ServiceUnavailableError extends ServiceError {
readonly statusCode = HttpStatusCodes.SERVICE_UNAVAILABLE;
readonly errorType = 'ServiceUnavailableError';

constructor(
Expand All @@ -161,5 +169,21 @@ export class ServiceUnavailableError extends ServiceError {
details?: Record<string, unknown>
) {
super(message, options, details);
this.name = 'ServiceUnavailableError';
}
}

export {
BadRequestError,
ForbiddenError,
InternalServerError,
MethodNotAllowedError,
NotFoundError,
ParameterValidationError,
RequestEntityTooLargeError,
RequestTimeoutError,
RouteMatchingError,
ServiceError,
ServiceUnavailableError,
UnauthorizedError,
};
2 changes: 1 addition & 1 deletion packages/event-handler/src/rest/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { HttpErrorCodes, HttpVerbs } from './constants.js';
export { HttpStatusCodes, HttpVerbs } from './constants.js';
export {
handlerResultToProxyResult,
handlerResultToWebResponse,
Expand Down
4 changes: 2 additions & 2 deletions packages/event-handler/src/rest/middleware/cors.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { CorsOptions, Middleware } from '../../types/rest.js';
import {
DEFAULT_CORS_OPTIONS,
HttpErrorCodes,
HttpStatusCodes,
HttpVerbs,
} from '../constants.js';

Expand Down Expand Up @@ -123,7 +123,7 @@ export const cors = (options?: CorsOptions): Middleware => {
reqCtx.res.headers.append('access-control-allow-headers', header);
}
return new Response(null, {
status: HttpErrorCodes.NO_CONTENT,
status: HttpStatusCodes.NO_CONTENT,
headers: reqCtx.res.headers,
});
}
Expand Down
4 changes: 2 additions & 2 deletions packages/event-handler/src/types/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
JSONObject,
} from '@aws-lambda-powertools/commons/types';
import type { APIGatewayProxyEvent, Context } from 'aws-lambda';
import type { HttpErrorCodes, HttpVerbs } from '../rest/constants.js';
import type { HttpStatusCodes, HttpVerbs } from '../rest/constants.js';
import type { Route } from '../rest/Route.js';
import type { Router } from '../rest/Router.js';
import type { ResolveOptions } from './common.js';
Expand Down Expand Up @@ -61,7 +61,7 @@ type RouteHandler<TReturn = HandlerResponse> = (

type HttpMethod = keyof typeof HttpVerbs;

type HttpStatusCode = (typeof HttpErrorCodes)[keyof typeof HttpErrorCodes];
type HttpStatusCode = (typeof HttpStatusCodes)[keyof typeof HttpStatusCodes];

type Path = `/${string}`;

Expand Down
Loading
Loading