@@ -6,7 +6,13 @@ import {
66 getStringFromEnv ,
77 isDevMode ,
88} from '@aws-lambda-powertools/commons/utils/env' ;
9- import type { APIGatewayProxyResult , Context } from 'aws-lambda' ;
9+ import type {
10+ APIGatewayProxyEvent ,
11+ APIGatewayProxyEventV2 ,
12+ APIGatewayProxyResult ,
13+ APIGatewayProxyStructuredResultV2 ,
14+ Context ,
15+ } from 'aws-lambda' ;
1016import type { HandlerResponse , ResolveOptions } from '../types/index.js' ;
1117import type {
1218 ErrorConstructor ,
@@ -24,15 +30,16 @@ import type {
2430} from '../types/rest.js' ;
2531import { HttpStatusCodes , HttpVerbs } from './constants.js' ;
2632import {
27- handlerResultToProxyResult ,
2833 handlerResultToWebResponse ,
2934 proxyEventToWebRequest ,
30- webHeadersToApiGatewayV1Headers ,
35+ webHeadersToApiGatewayHeaders ,
36+ webResponseToProxyResult ,
3137} from './converters.js' ;
3238import { ErrorHandlerRegistry } from './ErrorHandlerRegistry.js' ;
3339import {
3440 HttpError ,
35- InternalServerError ,
41+ InvalidEventError ,
42+ InvalidHttpMethodError ,
3643 MethodNotAllowedError ,
3744 NotFoundError ,
3845} from './errors.js' ;
@@ -41,9 +48,9 @@ import { RouteHandlerRegistry } from './RouteHandlerRegistry.js';
4148import {
4249 composeMiddleware ,
4350 HttpResponseStream ,
44- isAPIGatewayProxyEvent ,
51+ isAPIGatewayProxyEventV1 ,
52+ isAPIGatewayProxyEventV2 ,
4553 isExtendedAPIGatewayProxyResult ,
46- isHttpMethod ,
4754 resolvePrefixedPath ,
4855} from './utils.js' ;
4956
@@ -202,38 +209,45 @@ class Router {
202209 event : unknown ,
203210 context : Context ,
204211 options ?: ResolveOptions
205- ) : Promise < HandlerResponse > {
206- if ( ! isAPIGatewayProxyEvent ( event ) ) {
212+ ) : Promise < RequestContext > {
213+ if ( ! isAPIGatewayProxyEventV1 ( event ) && ! isAPIGatewayProxyEventV2 ( event ) ) {
207214 this . logger . error (
208215 'Received an event that is not compatible with this resolver'
209216 ) ;
210- throw new InternalServerError ( ) ;
217+ throw new InvalidEventError ( ) ;
211218 }
212219
213- const method = event . requestContext . httpMethod . toUpperCase ( ) ;
214- if ( ! isHttpMethod ( method ) ) {
215- this . logger . error ( `HTTP method ${ method } is not supported.` ) ;
216- // We can't throw a MethodNotAllowedError outside the try block as it
217- // will be converted to an internal server error by the API Gateway runtime
218- return {
219- statusCode : HttpStatusCodes . METHOD_NOT_ALLOWED ,
220- body : '' ,
221- } ;
220+ let req : Request ;
221+ try {
222+ req = proxyEventToWebRequest ( event ) ;
223+ } catch ( err ) {
224+ if ( err instanceof InvalidHttpMethodError ) {
225+ this . logger . error ( err ) ;
226+ // We can't throw a MethodNotAllowedError outside the try block as it
227+ // will be converted to an internal server error by the API Gateway runtime
228+ return {
229+ event,
230+ context,
231+ req : new Request ( 'https://invalid' ) ,
232+ res : new Response ( '' , { status : HttpStatusCodes . METHOD_NOT_ALLOWED } ) ,
233+ params : { } ,
234+ } ;
235+ }
236+ throw err ;
222237 }
223238
224- const req = proxyEventToWebRequest ( event ) ;
225-
226239 const requestContext : RequestContext = {
227240 event,
228241 context,
229242 req,
230243 // this response should be overwritten by the handler, if it isn't
231244 // it means something went wrong with the middleware chain
232- res : new Response ( '' , { status : 500 } ) ,
245+ res : new Response ( '' , { status : HttpStatusCodes . INTERNAL_SERVER_ERROR } ) ,
233246 params : { } ,
234247 } ;
235248
236249 try {
250+ const method = req . method as HttpMethod ;
237251 const path = new URL ( req . url ) . pathname as Path ;
238252
239253 const route = this . routeRegistry . resolve ( method , path ) ;
@@ -255,6 +269,7 @@ class Router {
255269 : route . handler . bind ( options . scope ) ;
256270
257271 const handlerResult = await handler ( reqCtx ) ;
272+
258273 reqCtx . res = handlerResultToWebResponse (
259274 handlerResult ,
260275 reqCtx . res . headers
@@ -277,13 +292,25 @@ class Router {
277292 } ) ;
278293
279294 // middleware result takes precedence to allow short-circuiting
280- return middlewareResult ?? requestContext . res ;
295+ if ( middlewareResult !== undefined ) {
296+ requestContext . res = handlerResultToWebResponse (
297+ middlewareResult ,
298+ requestContext . res . headers
299+ ) ;
300+ }
301+
302+ return requestContext ;
281303 } catch ( error ) {
282304 this . logger . debug ( `There was an error processing the request: ${ error } ` ) ;
283- return this . handleError ( error as Error , {
305+ const res = await this . handleError ( error as Error , {
284306 ...requestContext ,
285307 scope : options ?. scope ,
286308 } ) ;
309+ requestContext . res = handlerResultToWebResponse (
310+ res ,
311+ requestContext . res . headers
312+ ) ;
313+ return requestContext ;
287314 }
288315 }
289316
@@ -296,15 +323,30 @@ class Router {
296323 * @param event - The Lambda event to resolve
297324 * @param context - The Lambda context
298325 * @param options - Optional resolve options for scope binding
299- * @returns An API Gateway proxy result
326+ * @returns An API Gateway proxy result (V1 or V2 format depending on event version)
300327 */
328+ public async resolve (
329+ event : APIGatewayProxyEvent ,
330+ context : Context ,
331+ options ?: ResolveOptions
332+ ) : Promise < APIGatewayProxyResult > ;
333+ public async resolve (
334+ event : APIGatewayProxyEventV2 ,
335+ context : Context ,
336+ options ?: ResolveOptions
337+ ) : Promise < APIGatewayProxyStructuredResultV2 > ;
301338 public async resolve (
302339 event : unknown ,
303340 context : Context ,
304341 options ?: ResolveOptions
305- ) : Promise < APIGatewayProxyResult > {
306- const result = await this . #resolve( event , context , options ) ;
307- return handlerResultToProxyResult ( result ) ;
342+ ) : Promise < APIGatewayProxyResult | APIGatewayProxyStructuredResultV2 > ;
343+ public async resolve (
344+ event : unknown ,
345+ context : Context ,
346+ options ?: ResolveOptions
347+ ) : Promise < APIGatewayProxyResult | APIGatewayProxyStructuredResultV2 > {
348+ const reqCtx = await this . #resolve( event , context , options ) ;
349+ return webResponseToProxyResult ( reqCtx . res , reqCtx . event ) ;
308350 }
309351
310352 /**
@@ -321,31 +363,33 @@ class Router {
321363 context : Context ,
322364 options : ResolveStreamOptions
323365 ) : Promise < void > {
324- const result = await this . #resolve( event , context , options ) ;
325- await this . #streamHandlerResponse( result , options . responseStream ) ;
366+ const reqCtx = await this . #resolve( event , context , options ) ;
367+ await this . #streamHandlerResponse( reqCtx , options . responseStream ) ;
326368 }
327369
328370 /**
329371 * Streams a handler response to the Lambda response stream.
330372 * Converts the response to a web response and pipes it through the stream.
331373 *
332- * @param response - The handler response to stream
374+ * @param reqCtx - The request context containing the response to stream
333375 * @param responseStream - The Lambda response stream to write to
334376 */
335377 async #streamHandlerResponse(
336- response : HandlerResponse ,
378+ reqCtx : RequestContext ,
337379 responseStream : ResponseStream
338380 ) {
339- const webResponse = handlerResultToWebResponse ( response ) ;
340- const { headers } = webHeadersToApiGatewayV1Headers ( webResponse . headers ) ;
381+ const { headers } = webHeadersToApiGatewayHeaders (
382+ reqCtx . res . headers ,
383+ reqCtx . event
384+ ) ;
341385 const resStream = HttpResponseStream . from ( responseStream , {
342- statusCode : webResponse . status ,
386+ statusCode : reqCtx . res . status ,
343387 headers,
344388 } ) ;
345389
346- if ( webResponse . body ) {
390+ if ( reqCtx . res . body ) {
347391 const nodeStream = Readable . fromWeb (
348- webResponse . body as streamWeb . ReadableStream
392+ reqCtx . res . body as streamWeb . ReadableStream
349393 ) ;
350394 await pipeline ( nodeStream , resStream ) ;
351395 } else {
0 commit comments