Skip to content

Commit

Permalink
feat(type-safe-api): add s3 integration
Browse files Browse the repository at this point in the history
  • Loading branch information
valebedu committed Oct 5, 2023
1 parent 917a5db commit fa8def9
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/type-safe-api/src/construct/integrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from "./integration";
export * from "./integrations";
export * from "./lambda";
export * from "./mock";
export * from "./s3";
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ SPDX-License-Identifier: Apache-2.0 */
import { IFunction } from "aws-cdk-lib/aws-lambda";
import { LambdaIntegration } from "./lambda";
import { MockIntegration, MockIntegrationResponse } from "./mock";
import { S3Integration, S3IntegrationProps } from "./s3";

/**
* A collection of integrations to connect API operations with a backend to service requests
Expand All @@ -23,4 +24,12 @@ export class Integrations {
public static mock(response: MockIntegrationResponse): MockIntegration {
return new MockIntegration(response);
}

/**
* An integration that can read/write to an S3 bucket
* @param props the integration props
*/
public static s3(props: S3IntegrationProps): S3Integration {
return new S3Integration(props);
}
}
89 changes: 89 additions & 0 deletions packages/type-safe-api/src/construct/integrations/s3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0 */
import { IRole, Role, ServicePrincipal } from "aws-cdk-lib/aws-iam";
import { IBucket } from "aws-cdk-lib/aws-s3";
import {
ApiGatewayIntegration,
Integration,
IntegrationRenderProps,
} from "./integration";
import { generateCorsResponseParameters } from "../prepare-spec-event-handler/prepare-spec";
import { bucketInvocationUri } from "../spec/utils";

export interface S3IntegrationProps {
/**
* The S3 bucket to be invoked on integration
*/
readonly bucket: IBucket;

/**
* The IAM role to be used to grant permissions to the API Gateway to invoke the S3 bucket
* @default - a new role will be created
*/
readonly role?: IRole;

/**
* The path override for the integration
* @default - integration path is used
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/integrating-api-with-aws-services-s3.html
*/
readonly pathOverride?: string;
}

/**
* An S3 integration
*/
export class S3Integration extends Integration {
private readonly bucket: IBucket;
private readonly role: IRole;
private readonly pathOverride?: string;

constructor(props: S3IntegrationProps) {
super();

this.bucket = props.bucket;

if (props.role) {
this.role = props.role;
} else {
this.role = new Role(this.bucket, "Role", {
assumedBy: new ServicePrincipal("apigateway.amazonaws.com"),
});
}

this.bucket.grantReadWrite(this.role, this.pathOverride ?? "*");
this.pathOverride = props.pathOverride;
}

/**
* Render the lambda integration as a snippet of OpenAPI
*/
public render(props: IntegrationRenderProps): ApiGatewayIntegration {
return {
type: "AWS",
httpMethod: props.method.toUpperCase(),
uri: bucketInvocationUri(
props.scope,
this.bucket,
this.pathOverride ?? props.path
),
credentials: this.role.roleArn,
responses: {
default: {
statusCode: "200",
responseParameters: props.corsOptions
? generateCorsResponseParameters(props.corsOptions)
: {},
responseTemplates: {},
},
"4|5\\d{2}": {
statusCode: "500", // force 500 because 4XX errors is not a real client error, it's a misconfiguration on the server side
responseParameters: props.corsOptions
? generateCorsResponseParameters(props.corsOptions)
: {},
responseTemplates: {},
},
},
};
}
}
17 changes: 17 additions & 0 deletions packages/type-safe-api/src/construct/spec/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
SPDX-License-Identifier: Apache-2.0 */
import { Stack } from "aws-cdk-lib";
import { IFunction } from "aws-cdk-lib/aws-lambda";
import { IBucket } from "aws-cdk-lib/aws-s3";
import { Construct } from "constructs";

/**
Expand All @@ -16,3 +17,19 @@ export const functionInvocationUri = (
const stack = Stack.of(scope);
return `arn:${stack.partition}:apigateway:${stack.region}:lambda:path/2015-03-31/functions/${lambdaFunction.functionArn}/invocations`;
};

/**
* Generate the s3 bucket invocation uri for the given s3 within the given scope
* @param scope scope in which the s3 is deployed
* @param bucket the s3 bucket to be invoked
*/
export const bucketInvocationUri = (
scope: Construct,
bucket: IBucket,
pathOverride?: string
): string => {
const stack = Stack.of(scope);
return `arn:${stack.partition}:apigateway:${stack.region}:s3:path/${
bucket.bucketName
}/${pathOverride ?? ""}`;
};

0 comments on commit fa8def9

Please sign in to comment.