Skip to content

Commit 424732f

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/main/esbuild-0.25.10
2 parents f72dc1c + 7d93038 commit 424732f

File tree

17 files changed

+399
-46
lines changed

17 files changed

+399
-46
lines changed

docs/requirements.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ mkdocs-material==9.6.20
33
mkdocs-git-revision-date-plugin==0.3.2
44
mkdocs-exclude==1.0.2
55
mkdocs-typedoc==1.0.4
6-
mkdocs-llmstxt==0.3.1
6+
mkdocs-llmstxt==0.3.2

docs/requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,9 +274,9 @@ mkdocs-get-deps==0.2.0 \
274274
mkdocs-git-revision-date-plugin==0.3.2 \
275275
--hash=sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef
276276
# via -r requirements.in
277-
mkdocs-llmstxt==0.3.1 \
278-
--hash=sha256:123119d9b984c1d1224ed5af250bfbc49879ad83decdaff59d8b0ebb459ddc54 \
279-
--hash=sha256:31f5b6aaae6123c09a2b1c32912c3eb21ccb356b5db7abb867f105e8cc392653
277+
mkdocs-llmstxt==0.3.2 \
278+
--hash=sha256:dd63acb8257fca3244058fd820acd4700c1626dbe48ad3a1a2cc9c599f8e4b7f \
279+
--hash=sha256:fb363205d6f1452411dc5069f62012cb6b29e1788f6db9cc17793bdca7eabea8
280280
# via -r requirements.in
281281
mkdocs-material==9.6.20 \
282282
--hash=sha256:b8d8c8b0444c7c06dd984b55ba456ce731f0035c5a1533cc86793618eb1e6c82 \

examples/app/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
},
3030
"devDependencies": {
3131
"@types/aws-lambda": "^8.10.152",
32-
"@types/node": "24.5.1",
32+
"@types/node": "24.5.2",
3333
"aws-cdk-lib": "^2.214.0",
3434
"constructs": "^10.4.2",
3535
"source-map-support": "^0.5.21",
@@ -48,7 +48,7 @@
4848
"@aws-sdk/lib-dynamodb": "^3.888.0",
4949
"@middy/core": "^4.7.0",
5050
"@types/aws-lambda": "^8.10.152",
51-
"@types/node": "24.5.1",
51+
"@types/node": "24.5.2",
5252
"aws-cdk": "^2.1029.1",
5353
"constructs": "^10.4.2",
5454
"esbuild": "^0.25.10",

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"devDependencies": {
5353
"@biomejs/biome": "^2.2.4",
5454
"@types/aws-lambda": "^8.10.152",
55-
"@types/node": "^24.5.1",
55+
"@types/node": "^24.5.2",
5656
"@vitest/coverage-v8": "^3.2.4",
5757
"husky": "^9.1.7",
5858
"lint-staged": "^16.1.6",

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

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -226,33 +226,40 @@ class Router {
226226

227227
const route = this.routeRegistry.resolve(method, path);
228228

229-
if (route === null) {
230-
throw new NotFoundError(`Route ${path} for method ${method} not found`);
231-
}
232-
233-
const handler =
234-
options?.scope != null
235-
? route.handler.bind(options.scope)
236-
: route.handler;
237-
238229
const handlerMiddleware: Middleware = async (params, reqCtx, next) => {
239-
const handlerResult = await handler(params, reqCtx);
240-
reqCtx.res = handlerResultToWebResponse(
241-
handlerResult,
242-
reqCtx.res.headers
243-
);
230+
if (route === null) {
231+
const notFoundRes = await this.handleError(
232+
new NotFoundError(`Route ${path} for method ${method} not found`),
233+
{ ...reqCtx, scope: options?.scope }
234+
);
235+
reqCtx.res = handlerResultToWebResponse(
236+
notFoundRes,
237+
reqCtx.res.headers
238+
);
239+
} else {
240+
const handler =
241+
options?.scope != null
242+
? route.handler.bind(options.scope)
243+
: route.handler;
244+
245+
const handlerResult = await handler(params, reqCtx);
246+
reqCtx.res = handlerResultToWebResponse(
247+
handlerResult,
248+
reqCtx.res.headers
249+
);
250+
}
244251

245252
await next();
246253
};
247254

248255
const middleware = composeMiddleware([
249256
...this.middleware,
250-
...route.middleware,
257+
...(route?.middleware ?? []),
251258
handlerMiddleware,
252259
]);
253260

254261
const middlewareResult = await middleware(
255-
route.params,
262+
route?.params ?? {},
256263
requestContext,
257264
() => Promise.resolve()
258265
);

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,23 @@ export const SAFE_CHARS = "-._~()'!*:@,;=+&$";
8888

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

91+
/**
92+
* Default CORS configuration
93+
*/
94+
export const DEFAULT_CORS_OPTIONS = {
95+
origin: '*',
96+
allowMethods: ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'],
97+
allowHeaders: [
98+
'Authorization',
99+
'Content-Type',
100+
'X-Amz-Date',
101+
'X-Api-Key',
102+
'X-Amz-Security-Token',
103+
],
104+
exposeHeaders: [],
105+
credentials: false
106+
};
107+
91108
export const DEFAULT_COMPRESSION_RESPONSE_THRESHOLD = 1024;
92109

93110
export const CACHE_CONTROL_NO_TRANSFORM_REGEX =

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,14 @@ export const handlerResultToWebResponse = (
140140
resHeaders?: Headers
141141
): Response => {
142142
if (response instanceof Response) {
143-
return response;
143+
const headers = new Headers(resHeaders);
144+
for (const [key, value] of response.headers.entries()) {
145+
headers.set(key, value);
146+
}
147+
return new Response(response.body, {
148+
status: response.status,
149+
headers,
150+
});
144151
}
145152

146153
const headers = new Headers(resHeaders);
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import type {
2+
CorsOptions,
3+
Middleware,
4+
} from '../../types/rest.js';
5+
import {
6+
DEFAULT_CORS_OPTIONS,
7+
HttpErrorCodes,
8+
HttpVerbs,
9+
} from '../constants.js';
10+
11+
/**
12+
* Resolves the origin value based on the configuration
13+
*/
14+
const resolveOrigin = (
15+
originConfig: NonNullable<CorsOptions['origin']>,
16+
requestOrigin: string | null,
17+
): string => {
18+
if (Array.isArray(originConfig)) {
19+
return requestOrigin && originConfig.includes(requestOrigin) ? requestOrigin : '';
20+
}
21+
return originConfig;
22+
};
23+
24+
/**
25+
* Creates a CORS middleware that adds appropriate CORS headers to responses
26+
* and handles OPTIONS preflight requests.
27+
*
28+
* @example
29+
* ```typescript
30+
* import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
31+
* import { cors } from '@aws-lambda-powertools/event-handler/experimental-rest/middleware';
32+
*
33+
* const app = new Router();
34+
*
35+
* // Use default configuration
36+
* app.use(cors());
37+
*
38+
* // Custom configuration
39+
* app.use(cors({
40+
* origin: 'https://example.com',
41+
* allowMethods: ['GET', 'POST'],
42+
* credentials: true,
43+
* }));
44+
*
45+
* // Dynamic origin with function
46+
* app.use(cors({
47+
* origin: (origin, reqCtx) => {
48+
* const allowedOrigins = ['https://app.com', 'https://admin.app.com'];
49+
* return origin && allowedOrigins.includes(origin);
50+
* }
51+
* }));
52+
* ```
53+
*
54+
* @param options.origin - The origin to allow requests from
55+
* @param options.allowMethods - The HTTP methods to allow
56+
* @param options.allowHeaders - The headers to allow
57+
* @param options.exposeHeaders - The headers to expose
58+
* @param options.credentials - Whether to allow credentials
59+
* @param options.maxAge - The maximum age for the preflight response
60+
*/
61+
export const cors = (options?: CorsOptions): Middleware => {
62+
const config = {
63+
...DEFAULT_CORS_OPTIONS,
64+
...options
65+
};
66+
67+
return async (_params, reqCtx, next) => {
68+
const requestOrigin = reqCtx.request.headers.get('Origin');
69+
const resolvedOrigin = resolveOrigin(config.origin, requestOrigin);
70+
71+
reqCtx.res.headers.set('access-control-allow-origin', resolvedOrigin);
72+
if (resolvedOrigin !== '*') {
73+
reqCtx.res.headers.set('Vary', 'Origin');
74+
}
75+
config.allowMethods.forEach(method => {
76+
reqCtx.res.headers.append('access-control-allow-methods', method);
77+
});
78+
config.allowHeaders.forEach(header => {
79+
reqCtx.res.headers.append('access-control-allow-headers', header);
80+
});
81+
config.exposeHeaders.forEach(header => {
82+
reqCtx.res.headers.append('access-control-expose-headers', header);
83+
});
84+
reqCtx.res.headers.set('access-control-allow-credentials', config.credentials.toString());
85+
if (config.maxAge !== undefined) {
86+
reqCtx.res.headers.set('access-control-max-age', config.maxAge.toString());
87+
}
88+
89+
// Handle preflight OPTIONS request
90+
if (reqCtx.request.method === HttpVerbs.OPTIONS && reqCtx.request.headers.has('Access-Control-Request-Method')) {
91+
return new Response(null, {
92+
status: HttpErrorCodes.NO_CONTENT,
93+
headers: reqCtx.res.headers,
94+
});
95+
}
96+
await next();
97+
};
98+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { compress } from './compress.js';
2+
export { cors } from './cors.js';

0 commit comments

Comments
 (0)