Skip to content

Commit ebdd101

Browse files
authored
Merge pull request #10 from anttiviljami/main
Support custom User-Agent, better default traceability
2 parents 9a4409d + 1cb39cb commit ebdd101

File tree

2 files changed

+71
-12
lines changed

2 files changed

+71
-12
lines changed

src/openapi-lambda-adapters.test.ts

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { APIGatewayProxyStructuredResultV2 } from 'aws-lambda'
1+
import { APIGatewayProxyStructuredResultV2, Context } from 'aws-lambda'
22
import { AxiosRequestConfig } from 'axios'
33
import { AxiosError, HttpMethod, Operation } from 'openapi-client-axios'
44
import { convertAxiosToApiGw, convertApiGwToAxios } from './openapi-lambda-adapters'
@@ -26,7 +26,8 @@ describe('Adapt axios request/response to AWS Lambda Proxy Event/Response', () =
2626
// then
2727
const event = convertAxiosToApiGw(axiosConfig, operation)
2828
expect(event.rawPath).toEqual('/v1/users')
29-
expect(Object.keys(event.headers).length).toEqual(2)
29+
expect(Object.keys(event.headers)).toContain('Accept')
30+
expect(Object.keys(event.headers)).toContain('authorization')
3031
expect(event.pathParameters).toEqual({})
3132
expect(event.queryStringParameters).toEqual({})
3233
expect(event.rawQueryString).toEqual('')
@@ -204,7 +205,52 @@ describe('Adapt axios request/response to AWS Lambda Proxy Event/Response', () =
204205
expect(event.headers['x-null']).toBeUndefined()
205206
expect(event.headers['x-undefined']).toBeUndefined()
206207
})
207-
208+
209+
it('defaults to lambda arn the user agent when passed in the context', () => {
210+
// given
211+
const axiosConfig: AxiosRequestConfig = {
212+
method: 'get',
213+
url: '/v1/users',
214+
}
215+
const operation: Operation = {
216+
path: '/v1/users',
217+
method: HttpMethod.Get,
218+
responses: {}
219+
}
220+
221+
// then
222+
const event = convertAxiosToApiGw(axiosConfig, operation, {
223+
invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-function'
224+
} as Context)
225+
226+
expect(event.requestContext.authorizer.lambda)
227+
expect(event.requestContext.http.userAgent).toBe('lambda-invoke-arn:aws:lambda:us-east-1:123456789012:function:my-function')
228+
expect(event.headers['User-Agent']).toBe('lambda-invoke-arn:aws:lambda:us-east-1:123456789012:function:my-function')
229+
})
230+
231+
it('includes the user agent when passed in the request config', () => {
232+
// given
233+
const axiosConfig: AxiosRequestConfig = {
234+
method: 'get',
235+
url: '/v1/users',
236+
headers: {
237+
'User-Agent': 'daniel-api',
238+
}
239+
}
240+
const operation: Operation = {
241+
path: '/v1/users',
242+
method: HttpMethod.Get,
243+
responses: {}
244+
}
245+
246+
// then
247+
const event = convertAxiosToApiGw(axiosConfig, operation, {
248+
invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-function'
249+
} as Context)
250+
251+
expect(event.requestContext.http.userAgent).toBe('daniel-api')
252+
expect(event.headers['User-Agent']).toBe('daniel-api')
253+
})
208254
})
209255

210256
describe('Api GW Proxy Response to Axios Response', () => {
@@ -431,4 +477,4 @@ describe('Adapt axios request/response to AWS Lambda Proxy Event/Response', () =
431477

432478
})
433479

434-
})
480+
})

src/openapi-lambda-adapters.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import {
1+
import type {
22
APIGatewayProxyEventV2, APIGatewayProxyStructuredResultV2,
33
APIGatewayProxyEventQueryStringParameters, APIGatewayProxyEventPathParameters,
4+
APIGatewayProxyEventV2WithLambdaAuthorizer,
45
Context
56
} from 'aws-lambda'
67

@@ -30,7 +31,9 @@ const lambdaRunner = async (axiosConfig: AxiosRequestConfig, operation: Operatio
3031
.then((resp) => convertApiGwToAxios(resp, axiosConfig))
3132
}
3233

33-
export const convertAxiosToApiGw = (config: AxiosRequestConfig, operation: Operation, crtLambdaContext?: Context): APIGatewayProxyEventV2 => {
34+
export interface LambdaRunnerAuthContext { 'lambda-invoke': true, callerIdentity: string }
35+
36+
export const convertAxiosToApiGw = (config: AxiosRequestConfig, operation: Operation, crtLambdaContext?: Context): APIGatewayProxyEventV2WithLambdaAuthorizer<LambdaRunnerAuthContext> => {
3437
// extract path params
3538
// eg: for path template /v1/users/{id} & path url /v1/users/1108 -> will extract {'id': '1108'}
3639
const template = operation.path
@@ -61,7 +64,15 @@ export const convertAxiosToApiGw = (config: AxiosRequestConfig, operation: Opera
6164
headers[key] = val.toString()
6265
}
6366

64-
const lambdaPayload = {
67+
// identify caller lambda
68+
const sourceIdentity = ['lambda-invoke', crtLambdaContext?.invokedFunctionArn].filter(Boolean).join('-')
69+
70+
// default to lambda-invoke user-agent
71+
if (!headers['User-Agent'] && !headers['user-agent']) {
72+
headers['User-Agent'] = sourceIdentity
73+
}
74+
75+
const lambdaPayload: APIGatewayProxyEventV2WithLambdaAuthorizer<LambdaRunnerAuthContext> = {
6576
version: '2.0',
6677
routeKey: '$default',
6778
rawPath: config.url,
@@ -75,7 +86,7 @@ export const convertAxiosToApiGw = (config: AxiosRequestConfig, operation: Opera
7586
authorizer: {
7687
lambda: {
7788
'lambda-invoke': true,
78-
callerIdentity: crtLambdaContext?.invokedFunctionArn ?? 'lambda-invoke-not-specified'
89+
callerIdentity: sourceIdentity
7990
}
8091
},
8192
domainName: 'lambda-invoke',
@@ -85,7 +96,7 @@ export const convertAxiosToApiGw = (config: AxiosRequestConfig, operation: Opera
8596
sourceIp: '',
8697
path: config.url,
8798
protocol: 'HTTP/1.1',
88-
userAgent: 'lambda-invoke'
99+
userAgent: headers['user-agent'] ?? headers['User-Agent']
89100
},
90101
requestId: crtLambdaContext?.awsRequestId ?? `lambda-invoke-${uuidv4()}`,
91102
routeKey: '$default',
@@ -94,9 +105,11 @@ export const convertAxiosToApiGw = (config: AxiosRequestConfig, operation: Opera
94105
timeEpoch: Date.now()
95106
},
96107
body: config.data ? JSON.stringify(config.data) : '',
97-
isBase64Encoded: false,
98-
httpMethod: config.method
99-
} as APIGatewayProxyEventV2
108+
isBase64Encoded: false
109+
}
110+
111+
// for backwards compat with older event format
112+
Object.assign(lambdaPayload, { httpMethod: config.method })
100113

101114
debug('lambdaRequest %o', lambdaPayload)
102115

0 commit comments

Comments
 (0)