diff --git a/runtime-tests/lambda/index.test.ts b/runtime-tests/lambda/index.test.ts index adf9fc835..f4f3f8367 100644 --- a/runtime-tests/lambda/index.test.ts +++ b/runtime-tests/lambda/index.test.ts @@ -249,6 +249,7 @@ describe('AWS Lambda Adapter for Hono', () => { expect(response.statusCode).toBe(200) expect(response.body).toBe('Hello Lambda!') expect(response.headers['content-type']).toMatch(/^text\/plain/) + expect(response.multiValueHeaders).toBeUndefined() expect(response.isBase64Encoded).toBe(false) }) @@ -268,6 +269,7 @@ describe('AWS Lambda Adapter for Hono', () => { expect(response.statusCode).toBe(200) expect(response.body).toBe('RmFrZSBJbWFnZQ==') expect(response.headers['content-type']).toMatch(/^image\/png/) + expect(response.multiValueHeaders).toBeUndefined() expect(response.isBase64Encoded).toBe(true) }) @@ -289,6 +291,7 @@ describe('AWS Lambda Adapter for Hono', () => { expect(response.statusCode).toBe(200) expect(response.body).toBe('Hello Lambda!') expect(response.headers['content-type']).toMatch(/^text\/plain/) + expect(response.multiValueHeaders).toBeUndefined() expect(response.isBase64Encoded).toBe(false) }) @@ -309,6 +312,7 @@ describe('AWS Lambda Adapter for Hono', () => { expect(response.statusCode).toBe(200) expect(response.body).toBe('Hello Lambda!') expect(response.headers['content-type']).toMatch(/^text\/plain/) + expect(response.multiValueHeaders).toBeUndefined() expect(response.isBase64Encoded).toBe(false) }) @@ -540,6 +544,7 @@ describe('AWS Lambda Adapter for Hono', () => { 'content-type': 'application/json', }) ) + expect(albResponse.multiValueHeaders).toBeUndefined() }) it('Should extract single-value headers and return 200 (APIGatewayProxyEvent)', async () => { @@ -687,6 +692,7 @@ describe('AWS Lambda Adapter for Hono', () => { expect(albResponse.statusCode).toBe(200) expect(albResponse.body).toBe('Valid Cookies') expect(albResponse.headers['content-type']).toMatch(/^text\/plain/) + expect(albResponse.multiValueHeaders).toBeUndefined() expect(albResponse.isBase64Encoded).toBe(false) }) @@ -709,7 +715,10 @@ describe('AWS Lambda Adapter for Hono', () => { expect(albResponse.statusCode).toBe(200) expect(albResponse.body).toBe('Valid Cookies') - expect(albResponse.headers['content-type']).toMatch(/^text\/plain/) + expect(albResponse.headers).toBeUndefined() + expect(albResponse.multiValueHeaders['content-type']).toEqual([ + expect.stringMatching(/^text\/plain/), + ]) expect(albResponse.isBase64Encoded).toBe(false) }) @@ -759,9 +768,8 @@ describe('AWS Lambda Adapter for Hono', () => { expect(albResponse.statusCode).toBe(200) expect(albResponse.body).toBe('Cookies Set') - expect(albResponse.headers['content-type']).toMatch(/^text\/plain/) - expect(albResponse.multiValueHeaders).toBeDefined() - expect(albResponse.multiValueHeaders && albResponse.multiValueHeaders['set-cookie']).toEqual( + expect(albResponse.headers).toBeUndefined() + expect(albResponse.multiValueHeaders['set-cookie']).toEqual( expect.arrayContaining([testCookie1.serialized, testCookie2.serialized]) ) expect(albResponse.isBase64Encoded).toBe(false) @@ -794,6 +802,7 @@ describe('AWS Lambda Adapter for Hono', () => { }) ) expect(albResponse.headers['content-type']).toMatch(/^application\/json/) + expect(albResponse.multiValueHeaders).toBeUndefined() expect(albResponse.isBase64Encoded).toBe(false) }) @@ -823,7 +832,10 @@ describe('AWS Lambda Adapter for Hono', () => { key2: 'value2', }) ) - expect(albResponse.headers['content-type']).toMatch(/^application\/json/) + expect(albResponse.headers).toBeUndefined() + expect(albResponse.multiValueHeaders['content-type']).toEqual([ + expect.stringMatching(/^application\/json/), + ]) expect(albResponse.isBase64Encoded).toBe(false) }) @@ -853,7 +865,10 @@ describe('AWS Lambda Adapter for Hono', () => { key2: ['value2', 'otherValue2'], }) ) - expect(albResponse.headers['content-type']).toMatch(/^application\/json/) + expect(albResponse.headers).toBeUndefined() + expect(albResponse.multiValueHeaders['content-type']).toEqual([ + expect.stringMatching(/^application\/json/), + ]) expect(albResponse.isBase64Encoded).toBe(false) }) }) diff --git a/src/adapter/aws-lambda/handler.ts b/src/adapter/aws-lambda/handler.ts index a8bb83461..bbe84cad4 100644 --- a/src/adapter/aws-lambda/handler.ts +++ b/src/adapter/aws-lambda/handler.ts @@ -75,17 +75,22 @@ export interface ALBProxyEvent { requestContext: ALBRequestContext } -export interface APIGatewayProxyResult { +type WithHeaders = { + headers: Record + multiValueHeaders?: undefined +} +type WithMultiValueHeaders = { + headers?: undefined + multiValueHeaders: Record +} + +export type APIGatewayProxyResult = { statusCode: number statusDescription?: string body: string - headers: Record cookies?: string[] - multiValueHeaders?: { - [headerKey: string]: string[] - } isBase64Encoded: boolean -} +} & (WithHeaders | WithMultiValueHeaders) const getRequestContext = ( event: LambdaEvent @@ -167,7 +172,16 @@ export const streamHandle = < */ export const handle = ( app: Hono -): ((event: LambdaEvent, lambdaContext?: LambdaContext) => Promise) => { +): (( + event: L, + lambdaContext?: LambdaContext +) => Promise< + APIGatewayProxyResult & + (L extends { multiValueHeaders: Record } + ? WithMultiValueHeaders + : WithHeaders) +>) => { + // @ts-expect-error FIXME: Fix return typing return async (event, lambdaContext?) => { const processor = getProcessor(event) @@ -195,11 +209,7 @@ export abstract class EventProcessor { protected abstract getCookies(event: E, headers: Headers): void - protected abstract setCookiesToResult( - event: E, - result: APIGatewayProxyResult, - cookies: string[] - ): void + protected abstract setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void createRequest(event: E): Request { const queryString = this.getQueryString(event) @@ -239,19 +249,27 @@ export abstract class EventProcessor { const result: APIGatewayProxyResult = { body: body, - headers: {}, - multiValueHeaders: event.multiValueHeaders ? {} : undefined, statusCode: res.status, isBase64Encoded, + ...(event.multiValueHeaders + ? { + multiValueHeaders: {}, + } + : { + headers: {}, + }), } this.setCookies(event, res, result) - res.headers.forEach((value, key) => { - result.headers[key] = value - if (event.multiValueHeaders && result.multiValueHeaders) { + if (result.multiValueHeaders) { + res.headers.forEach((value, key) => { result.multiValueHeaders[key] = [value] - } - }) + }) + } else { + res.headers.forEach((value, key) => { + result.headers[key] = value + }) + } return result } @@ -265,7 +283,7 @@ export abstract class EventProcessor { .map(([, v]) => v) if (Array.isArray(cookies)) { - this.setCookiesToResult(event, result, cookies) + this.setCookiesToResult(result, cookies) res.headers.delete('set-cookie') } } @@ -291,11 +309,7 @@ export class EventV2Processor extends EventProcessor { } } - protected setCookiesToResult( - _: APIGatewayProxyEventV2, - result: APIGatewayProxyResult, - cookies: string[] - ): void { + protected setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void { result.cookies = cookies } @@ -370,11 +384,7 @@ export class EventV1Processor extends EventProcessor { } } - protected setCookiesToResult( - event: ALBProxyEvent, - result: APIGatewayProxyResult, - cookies: string[] - ): void { + protected setCookiesToResult(result: APIGatewayProxyResult, cookies: string[]): void { // when multi value headers is enabled - if (event.multiValueHeaders && result.multiValueHeaders) { + if (result.multiValueHeaders) { result.multiValueHeaders['set-cookie'] = cookies } else { // otherwise serialize the set-cookie