@@ -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 ,
@@ -18,21 +24,23 @@ import type {
1824 RequestContext ,
1925 ResolveStreamOptions ,
2026 ResponseStream ,
27+ ResponseType ,
2128 RestRouteOptions ,
2229 RestRouterOptions ,
2330 RouteHandler ,
2431} from '../types/rest.js' ;
2532import { HttpStatusCodes , HttpVerbs } from './constants.js' ;
2633import {
27- handlerResultToProxyResult ,
2834 handlerResultToWebResponse ,
2935 proxyEventToWebRequest ,
30- webHeadersToApiGatewayV1Headers ,
36+ webHeadersToApiGatewayHeaders ,
37+ webResponseToProxyResult ,
3138} from './converters.js' ;
3239import { ErrorHandlerRegistry } from './ErrorHandlerRegistry.js' ;
3340import {
3441 HttpError ,
35- InternalServerError ,
42+ InvalidEventError ,
43+ InvalidHttpMethodError ,
3644 MethodNotAllowedError ,
3745 NotFoundError ,
3846} from './errors.js' ;
@@ -41,9 +49,9 @@ import { RouteHandlerRegistry } from './RouteHandlerRegistry.js';
4149import {
4250 composeMiddleware ,
4351 HttpResponseStream ,
44- isAPIGatewayProxyEvent ,
52+ isAPIGatewayProxyEventV1 ,
53+ isAPIGatewayProxyEventV2 ,
4554 isExtendedAPIGatewayProxyResult ,
46- isHttpMethod ,
4755 resolvePrefixedPath ,
4856} from './utils.js' ;
4957
@@ -202,38 +210,51 @@ class Router {
202210 event : unknown ,
203211 context : Context ,
204212 options ?: ResolveOptions
205- ) : Promise < HandlerResponse > {
206- if ( ! isAPIGatewayProxyEvent ( event ) ) {
213+ ) : Promise < RequestContext > {
214+ if ( ! isAPIGatewayProxyEventV1 ( event ) && ! isAPIGatewayProxyEventV2 ( event ) ) {
207215 this . logger . error (
208216 'Received an event that is not compatible with this resolver'
209217 ) ;
210- throw new InternalServerError ( ) ;
218+ throw new InvalidEventError ( ) ;
211219 }
212220
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- } ;
222- }
221+ const responseType : ResponseType = isAPIGatewayProxyEventV2 ( event )
222+ ? 'ApiGatewayV2'
223+ : 'ApiGatewayV1' ;
223224
224- const req = proxyEventToWebRequest ( event ) ;
225+ let req : Request ;
226+ try {
227+ req = proxyEventToWebRequest ( event ) ;
228+ } catch ( err ) {
229+ if ( err instanceof InvalidHttpMethodError ) {
230+ this . logger . error ( err ) ;
231+ // We can't throw a MethodNotAllowedError outside the try block as it
232+ // will be converted to an internal server error by the API Gateway runtime
233+ return {
234+ event,
235+ context,
236+ req : new Request ( 'https://invalid' ) ,
237+ res : new Response ( '' , { status : HttpStatusCodes . METHOD_NOT_ALLOWED } ) ,
238+ params : { } ,
239+ responseType,
240+ } ;
241+ }
242+ throw err ;
243+ }
225244
226245 const requestContext : RequestContext = {
227246 event,
228247 context,
229248 req,
230249 // this response should be overwritten by the handler, if it isn't
231250 // it means something went wrong with the middleware chain
232- res : new Response ( '' , { status : 500 } ) ,
251+ res : new Response ( '' , { status : HttpStatusCodes . INTERNAL_SERVER_ERROR } ) ,
233252 params : { } ,
253+ responseType,
234254 } ;
235255
236256 try {
257+ const method = req . method as HttpMethod ;
237258 const path = new URL ( req . url ) . pathname as Path ;
238259
239260 const route = this . routeRegistry . resolve ( method , path ) ;
@@ -255,6 +276,7 @@ class Router {
255276 : route . handler . bind ( options . scope ) ;
256277
257278 const handlerResult = await handler ( reqCtx ) ;
279+
258280 reqCtx . res = handlerResultToWebResponse (
259281 handlerResult ,
260282 reqCtx . res . headers
@@ -277,13 +299,25 @@ class Router {
277299 } ) ;
278300
279301 // middleware result takes precedence to allow short-circuiting
280- return middlewareResult ?? requestContext . res ;
302+ if ( middlewareResult !== undefined ) {
303+ requestContext . res = handlerResultToWebResponse (
304+ middlewareResult ,
305+ requestContext . res . headers
306+ ) ;
307+ }
308+
309+ return requestContext ;
281310 } catch ( error ) {
282311 this . logger . debug ( `There was an error processing the request: ${ error } ` ) ;
283- return this . handleError ( error as Error , {
312+ const res = await this . handleError ( error as Error , {
284313 ...requestContext ,
285314 scope : options ?. scope ,
286315 } ) ;
316+ requestContext . res = handlerResultToWebResponse (
317+ res ,
318+ requestContext . res . headers
319+ ) ;
320+ return requestContext ;
287321 }
288322 }
289323
@@ -296,15 +330,30 @@ class Router {
296330 * @param event - The Lambda event to resolve
297331 * @param context - The Lambda context
298332 * @param options - Optional resolve options for scope binding
299- * @returns An API Gateway proxy result
333+ * @returns An API Gateway proxy result (V1 or V2 format depending on event version)
300334 */
335+ public async resolve (
336+ event : APIGatewayProxyEvent ,
337+ context : Context ,
338+ options ?: ResolveOptions
339+ ) : Promise < APIGatewayProxyResult > ;
340+ public async resolve (
341+ event : APIGatewayProxyEventV2 ,
342+ context : Context ,
343+ options ?: ResolveOptions
344+ ) : Promise < APIGatewayProxyStructuredResultV2 > ;
301345 public async resolve (
302346 event : unknown ,
303347 context : Context ,
304348 options ?: ResolveOptions
305- ) : Promise < APIGatewayProxyResult > {
306- const result = await this . #resolve( event , context , options ) ;
307- return handlerResultToProxyResult ( result ) ;
349+ ) : Promise < APIGatewayProxyResult | APIGatewayProxyStructuredResultV2 > ;
350+ public async resolve (
351+ event : unknown ,
352+ context : Context ,
353+ options ?: ResolveOptions
354+ ) : Promise < APIGatewayProxyResult | APIGatewayProxyStructuredResultV2 > {
355+ const reqCtx = await this . #resolve( event , context , options ) ;
356+ return webResponseToProxyResult ( reqCtx . res , reqCtx . responseType ) ;
308357 }
309358
310359 /**
@@ -321,31 +370,33 @@ class Router {
321370 context : Context ,
322371 options : ResolveStreamOptions
323372 ) : Promise < void > {
324- const result = await this . #resolve( event , context , options ) ;
325- await this . #streamHandlerResponse( result , options . responseStream ) ;
373+ const reqCtx = await this . #resolve( event , context , options ) ;
374+ await this . #streamHandlerResponse( reqCtx , options . responseStream ) ;
326375 }
327376
328377 /**
329378 * Streams a handler response to the Lambda response stream.
330379 * Converts the response to a web response and pipes it through the stream.
331380 *
332- * @param response - The handler response to stream
381+ * @param reqCtx - The request context containing the response to stream
333382 * @param responseStream - The Lambda response stream to write to
334383 */
335384 async #streamHandlerResponse(
336- response : HandlerResponse ,
385+ reqCtx : RequestContext ,
337386 responseStream : ResponseStream
338387 ) {
339- const webResponse = handlerResultToWebResponse ( response ) ;
340- const { headers } = webHeadersToApiGatewayV1Headers ( webResponse . headers ) ;
388+ const { headers } = webHeadersToApiGatewayHeaders (
389+ reqCtx . res . headers ,
390+ reqCtx . responseType
391+ ) ;
341392 const resStream = HttpResponseStream . from ( responseStream , {
342- statusCode : webResponse . status ,
393+ statusCode : reqCtx . res . status ,
343394 headers,
344395 } ) ;
345396
346- if ( webResponse . body ) {
397+ if ( reqCtx . res . body ) {
347398 const nodeStream = Readable . fromWeb (
348- webResponse . body as streamWeb . ReadableStream
399+ reqCtx . res . body as streamWeb . ReadableStream
349400 ) ;
350401 await pipeline ( nodeStream , resStream ) ;
351402 } else {
0 commit comments