@@ -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,43 @@ 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+ return {
227+ event,
228+ context,
229+ req : new Request ( 'https://invalid' ) ,
230+ res : new Response ( '' , { status : HttpStatusCodes . METHOD_NOT_ALLOWED } ) ,
231+ params : { } ,
232+ } ;
233+ }
234+ throw err ;
222235 }
223236
224- const req = proxyEventToWebRequest ( event ) ;
225-
226237 const requestContext : RequestContext = {
227238 event,
228239 context,
229240 req,
230241 // this response should be overwritten by the handler, if it isn't
231242 // it means something went wrong with the middleware chain
232- res : new Response ( '' , { status : 500 } ) ,
243+ res : new Response ( '' , { status : HttpStatusCodes . INTERNAL_SERVER_ERROR } ) ,
233244 params : { } ,
234245 } ;
235246
236247 try {
248+ const method = req . method as HttpMethod ;
237249 const path = new URL ( req . url ) . pathname as Path ;
238250
239251 const route = this . routeRegistry . resolve ( method , path ) ;
@@ -255,6 +267,7 @@ class Router {
255267 : route . handler . bind ( options . scope ) ;
256268
257269 const handlerResult = await handler ( reqCtx ) ;
270+
258271 reqCtx . res = handlerResultToWebResponse (
259272 handlerResult ,
260273 reqCtx . res . headers
@@ -277,13 +290,25 @@ class Router {
277290 } ) ;
278291
279292 // middleware result takes precedence to allow short-circuiting
280- return middlewareResult ?? requestContext . res ;
293+ if ( middlewareResult !== undefined ) {
294+ requestContext . res = handlerResultToWebResponse (
295+ middlewareResult ,
296+ requestContext . res . headers
297+ ) ;
298+ }
299+
300+ return requestContext ;
281301 } catch ( error ) {
282302 this . logger . debug ( `There was an error processing the request: ${ error } ` ) ;
283- return this . handleError ( error as Error , {
303+ const res = await this . handleError ( error as Error , {
284304 ...requestContext ,
285305 scope : options ?. scope ,
286306 } ) ;
307+ requestContext . res = handlerResultToWebResponse (
308+ res ,
309+ requestContext . res . headers
310+ ) ;
311+ return requestContext ;
287312 }
288313 }
289314
@@ -296,15 +321,30 @@ class Router {
296321 * @param event - The Lambda event to resolve
297322 * @param context - The Lambda context
298323 * @param options - Optional resolve options for scope binding
299- * @returns An API Gateway proxy result
324+ * @returns An API Gateway proxy result (V1 or V2 format depending on event version)
300325 */
326+ public async resolve (
327+ event : APIGatewayProxyEvent ,
328+ context : Context ,
329+ options ?: ResolveOptions
330+ ) : Promise < APIGatewayProxyResult > ;
331+ public async resolve (
332+ event : APIGatewayProxyEventV2 ,
333+ context : Context ,
334+ options ?: ResolveOptions
335+ ) : Promise < APIGatewayProxyStructuredResultV2 > ;
301336 public async resolve (
302337 event : unknown ,
303338 context : Context ,
304339 options ?: ResolveOptions
305- ) : Promise < APIGatewayProxyResult > {
306- const result = await this . #resolve( event , context , options ) ;
307- return handlerResultToProxyResult ( result ) ;
340+ ) : Promise < APIGatewayProxyResult | APIGatewayProxyStructuredResultV2 > ;
341+ public async resolve (
342+ event : unknown ,
343+ context : Context ,
344+ options ?: ResolveOptions
345+ ) : Promise < APIGatewayProxyResult | APIGatewayProxyStructuredResultV2 > {
346+ const reqCtx = await this . #resolve( event , context , options ) ;
347+ return webResponseToProxyResult ( reqCtx . res , reqCtx . event ) ;
308348 }
309349
310350 /**
@@ -321,31 +361,33 @@ class Router {
321361 context : Context ,
322362 options : ResolveStreamOptions
323363 ) : Promise < void > {
324- const result = await this . #resolve( event , context , options ) ;
325- await this . #streamHandlerResponse( result , options . responseStream ) ;
364+ const reqCtx = await this . #resolve( event , context , options ) ;
365+ await this . #streamHandlerResponse( reqCtx , options . responseStream ) ;
326366 }
327367
328368 /**
329369 * Streams a handler response to the Lambda response stream.
330370 * Converts the response to a web response and pipes it through the stream.
331371 *
332- * @param response - The handler response to stream
372+ * @param reqCtx - The request context containing the response to stream
333373 * @param responseStream - The Lambda response stream to write to
334374 */
335375 async #streamHandlerResponse(
336- response : HandlerResponse ,
376+ reqCtx : RequestContext ,
337377 responseStream : ResponseStream
338378 ) {
339- const webResponse = handlerResultToWebResponse ( response ) ;
340- const { headers } = webHeadersToApiGatewayV1Headers ( webResponse . headers ) ;
379+ const { headers } = webHeadersToApiGatewayHeaders (
380+ reqCtx . res . headers ,
381+ reqCtx . event
382+ ) ;
341383 const resStream = HttpResponseStream . from ( responseStream , {
342- statusCode : webResponse . status ,
384+ statusCode : reqCtx . res . status ,
343385 headers,
344386 } ) ;
345387
346- if ( webResponse . body ) {
388+ if ( reqCtx . res . body ) {
347389 const nodeStream = Readable . fromWeb (
348- webResponse . body as streamWeb . ReadableStream
390+ reqCtx . res . body as streamWeb . ReadableStream
349391 ) ;
350392 await pipeline ( nodeStream , resStream ) ;
351393 } else {
0 commit comments