Skip to content

Commit 4f14152

Browse files
committed
feat: add api gateway lambda endpoint
1 parent f4dbbaf commit 4f14152

4 files changed

Lines changed: 123 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ _site/
2727
.jekyll-metadata
2828
vendor
2929
.trivy_out/
30+
*.tgz
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import {IResource, LambdaIntegration} from "aws-cdk-lib/aws-apigateway"
2+
import {IRole} from "aws-cdk-lib/aws-iam"
3+
import {IFunction} from "aws-cdk-lib/aws-lambda"
4+
import {HttpMethod} from "aws-cdk-lib/aws-lambda"
5+
import {Construct} from "constructs"
6+
7+
export interface LambdaFunctionHolder {
8+
readonly function: IFunction
9+
}
10+
11+
export interface LambdaEndpointProps {
12+
parentResource: IResource
13+
readonly resourceName: string
14+
readonly method: HttpMethod
15+
restApiGatewayRole: IRole
16+
lambdaFunction: LambdaFunctionHolder
17+
}
18+
19+
export class LambdaEndpoint extends Construct {
20+
resource: IResource
21+
22+
public constructor(scope: Construct, id: string, props: LambdaEndpointProps) {
23+
super(scope, id)
24+
25+
const resource = props.parentResource.addResource(props.resourceName)
26+
resource.addMethod(props.method, new LambdaIntegration(props.lambdaFunction.function, {
27+
credentialsRole: props.restApiGatewayRole
28+
}))
29+
30+
this.resource = resource
31+
}
32+
}

packages/cdkConstructs/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
export * from "./constructs/TypescriptLambdaFunction.js"
33
export * from "./constructs/RestApiGateway.js"
44
export * from "./constructs/RestApiGateway/accessLogFormat.js"
5+
export * from "./constructs/RestApiGateway/LambdaEndpoint.js"
56
export * from "./constructs/PythonLambdaFunction.js"
67
export * from "./apps/createApp.js"
78
export * from "./config/index.js"
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {App, Stack} from "aws-cdk-lib"
2+
import {RestApi} from "aws-cdk-lib/aws-apigateway"
3+
import {Role, ServicePrincipal} from "aws-cdk-lib/aws-iam"
4+
import {Template, Match} from "aws-cdk-lib/assertions"
5+
import {Architecture, Function as LambdaFunction, Runtime} from "aws-cdk-lib/aws-lambda"
6+
import {
7+
describe,
8+
test,
9+
beforeAll,
10+
expect
11+
} from "vitest"
12+
import {HttpMethod} from "aws-cdk-lib/aws-lambda"
13+
14+
import {LambdaEndpoint} from "../../../src/constructs/RestApiGateway/LambdaEndpoint.js"
15+
16+
describe("LambdaEndpoint construct", () => {
17+
let stack: Stack
18+
let template: Template
19+
let construct: LambdaEndpoint
20+
21+
beforeAll(() => {
22+
const app = new App()
23+
stack = new Stack(app, "LambdaEndpointStack")
24+
25+
const api = new RestApi(stack, "TestApi")
26+
27+
const credentialsRole = new Role(stack, "ApiGwRole", {
28+
assumedBy: new ServicePrincipal("apigateway.amazonaws.com")
29+
})
30+
31+
// Minimal lambda function stub that satisfies LambdaFunctionHolder interface
32+
const lambdaFn = new LambdaFunction(stack, "DummyFn", {
33+
runtime: Runtime.NODEJS_22_X,
34+
handler: "index.handler",
35+
code: {
36+
bind: () => ({
37+
s3Location: {bucketName: "dummy", objectKey: "dummy.zip"}
38+
}),
39+
bindToResource: () => undefined,
40+
isInline: false
41+
} as unknown as never,
42+
architecture: Architecture.X86_64
43+
})
44+
45+
construct = new LambdaEndpoint(stack, "TestLambdaEndpoint", {
46+
parentResource: api.root,
47+
resourceName: "test-resource",
48+
method: HttpMethod.GET,
49+
restApiGatewayRole: credentialsRole,
50+
lambdaFunction: {function: lambdaFn}
51+
})
52+
53+
template = Template.fromStack(stack)
54+
})
55+
56+
test("creates an API Gateway resource with the correct path part", () => {
57+
template.hasResourceProperties("AWS::ApiGateway::Resource", {
58+
PathPart: "test-resource"
59+
})
60+
})
61+
62+
test("creates a GET method on the resource", () => {
63+
template.hasResourceProperties("AWS::ApiGateway::Method", {
64+
HttpMethod: "GET"
65+
})
66+
})
67+
68+
test("exposes the resource as a public property", () => {
69+
expect(construct.resource).toBeDefined()
70+
})
71+
72+
test("uses credentials role on the Lambda integration", () => {
73+
template.hasResourceProperties("AWS::ApiGateway::Method", {
74+
HttpMethod: "GET",
75+
Integration: Match.objectLike({
76+
Type: "AWS_PROXY"
77+
})
78+
})
79+
})
80+
})
81+
82+
describe("LambdaEndpoint accepts TypescriptLambdaFunction via structural typing", () => {
83+
test("LambdaFunctionHolder interface is satisfied by any object with a function property", () => {
84+
// This is a compile-time check verified by the build step. Here we just
85+
// assert the interface shape is correct at runtime.
86+
const holder = {function: {} as unknown as never}
87+
expect(holder.function).toBeDefined()
88+
})
89+
})

0 commit comments

Comments
 (0)