Skip to content

Commit 3b5b97a

Browse files
author
Niranjan Jayakar
authoredNov 26, 2021
fix(apigatewayv2): integration class does not render an integration resource (#17729)
Routes on APIGateway V2 can integrate with different backends. This is achieved by creating the CFN resource `AWS::ApiGatewayV2::Integration` that is then referenced in the resource for the Route. Currently, the `IHttpRouteIntegration` (and `IWebSocketRouteIntegration`) interface represents a unique backend that a route can integrate with, using the CDK "bind" pattern. An integration can be bound to any number of routes but should be rendered into a single instance of `AWS::ApiGatewayV2::Integration` resource. To achieve this currently, the `HttpApi` (and `WebSocketApi`) class holds a cache of all integrations defined against its routes. This is the wrong level of caching and causes a number of problems. 1. We rely on the configuration of the `AWS::ApiGateway::Integration` resource to determine if one already exists. This means that two instances of `IHttpRouteIntegration` can result in rendering only one instance of `AWS::ApiGateway::Integration` resource. Users may want to intentionally generate multiple instances of `AWS::ApiGateway::Integration` classes with the same configuration. Taking away this power with CDK "magic" is just confusing. 2. Currently, we allow using the same instance of `IHttpRouteIntegration` (or `IWebSocketRouteIntegration`) to be bound to routes in different `HttpApi`. When bound to the route, the CDK renders an instance of `AWS::ApiGatewayV2::Integration` for each API. This is another "magic" that has the potential for user confusion and bugs. The solution is to KeepItSimple™. Remove the API level caching and simply cache at the level of each integration. This ensures that each instance of `HttpRouteIntegration` (previously `IHttpRouteIntegration`) renders to exactly one instance of `AWS::ApiGatewayV2::Integration`. Disallow using the same instance of `HttpRouteIntegration` across different instances of `HttpApi`. fixes #13213 BREAKING CHANGE: The interface `IHttpRouteIntegration` is replaced by the abstract class `HttpRouteIntegration`. * **apigatewayv2:** The interface `IWebSocketRouteIntegration` is now replaced by the abstract class `WebSocketRouteIntegration`. * **apigatewayv2:** Previously, we allowed the usage of integration classes to be used with routes defined in multiple `HttpApi` instances (or `WebSocketApi` instances). This is now disallowed, and separate instances must be created for each instance of `HttpApi` or `WebSocketApi`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 8191f1f commit 3b5b97a

File tree

19 files changed

+172
-155
lines changed

19 files changed

+172
-155
lines changed
 

‎packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/jwt.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Template } from '@aws-cdk/assertions';
2-
import { HttpApi, HttpIntegrationType, HttpRouteIntegrationBindOptions, IHttpRouteIntegration, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2';
2+
import { HttpApi, HttpIntegrationType, HttpRouteIntegrationBindOptions, HttpRouteIntegration, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2';
33
import { Stack } from '@aws-cdk/core';
44
import { HttpJwtAuthorizer } from '../../lib';
55

@@ -59,7 +59,7 @@ describe('HttpJwtAuthorizer', () => {
5959
});
6060
});
6161

62-
class DummyRouteIntegration implements IHttpRouteIntegration {
62+
class DummyRouteIntegration extends HttpRouteIntegration {
6363
public bind(_: HttpRouteIntegrationBindOptions) {
6464
return {
6565
payloadFormatVersion: PayloadFormatVersion.VERSION_2_0,

‎packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/lambda.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Match, Template } from '@aws-cdk/assertions';
2-
import { HttpApi, HttpIntegrationType, HttpRouteIntegrationBindOptions, IHttpRouteIntegration, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2';
2+
import { HttpApi, HttpIntegrationType, HttpRouteIntegrationBindOptions, HttpRouteIntegration, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2';
33
import { Code, Function, Runtime } from '@aws-cdk/aws-lambda';
44
import { Duration, Stack } from '@aws-cdk/core';
55
import { HttpLambdaAuthorizer, HttpLambdaResponseType } from '../../lib';
@@ -170,7 +170,7 @@ describe('HttpLambdaAuthorizer', () => {
170170
});
171171
});
172172

173-
class DummyRouteIntegration implements IHttpRouteIntegration {
173+
class DummyRouteIntegration extends HttpRouteIntegration {
174174
public bind(_: HttpRouteIntegrationBindOptions) {
175175
return {
176176
payloadFormatVersion: PayloadFormatVersion.VERSION_2_0,

‎packages/@aws-cdk/aws-apigatewayv2-authorizers/test/http/user-pool.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Template } from '@aws-cdk/assertions';
2-
import { HttpApi, HttpIntegrationType, HttpRouteIntegrationBindOptions, IHttpRouteIntegration, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2';
2+
import { HttpApi, HttpIntegrationType, HttpRouteIntegrationBindOptions, HttpRouteIntegration, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2';
33
import { UserPool } from '@aws-cdk/aws-cognito';
44
import { Stack } from '@aws-cdk/core';
55
import { HttpUserPoolAuthorizer } from '../../lib';
@@ -112,7 +112,7 @@ describe('HttpUserPoolAuthorizer', () => {
112112
});
113113
});
114114

115-
class DummyRouteIntegration implements IHttpRouteIntegration {
115+
class DummyRouteIntegration extends HttpRouteIntegration {
116116
public bind(_: HttpRouteIntegrationBindOptions) {
117117
return {
118118
payloadFormatVersion: PayloadFormatVersion.VERSION_2_0,

‎packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
HttpRouteIntegrationBindOptions,
44
HttpRouteIntegrationConfig,
55
HttpMethod,
6-
IHttpRouteIntegration,
6+
HttpRouteIntegration,
77
ParameterMapping,
88
PayloadFormatVersion,
99
} from '@aws-cdk/aws-apigatewayv2';
@@ -34,8 +34,9 @@ export interface HttpProxyIntegrationProps {
3434
/**
3535
* The HTTP Proxy integration resource for HTTP API
3636
*/
37-
export class HttpProxyIntegration implements IHttpRouteIntegration {
37+
export class HttpProxyIntegration extends HttpRouteIntegration {
3838
constructor(private readonly props: HttpProxyIntegrationProps) {
39+
super();
3940
}
4041

4142
public bind(_: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig {

‎packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
HttpIntegrationType,
33
HttpRouteIntegrationBindOptions,
44
HttpRouteIntegrationConfig,
5-
IHttpRouteIntegration,
5+
HttpRouteIntegration,
66
PayloadFormatVersion,
77
ParameterMapping,
88
} from '@aws-cdk/aws-apigatewayv2';
@@ -37,9 +37,10 @@ export interface LambdaProxyIntegrationProps {
3737
/**
3838
* The Lambda Proxy integration resource for HTTP API
3939
*/
40-
export class LambdaProxyIntegration implements IHttpRouteIntegration {
40+
export class LambdaProxyIntegration extends HttpRouteIntegration {
4141

4242
constructor(private readonly props: LambdaProxyIntegrationProps) {
43+
super();
4344
}
4445

4546
public bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig {

‎packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/private/integration.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
HttpIntegrationType,
44
HttpRouteIntegrationBindOptions,
55
HttpRouteIntegrationConfig,
6-
IHttpRouteIntegration,
6+
HttpRouteIntegration,
77
PayloadFormatVersion,
88
HttpMethod,
99
IVpcLink,
@@ -37,7 +37,7 @@ export interface VpcLinkConfigurationOptions {
3737
*
3838
* @internal
3939
*/
40-
export abstract class HttpPrivateIntegration implements IHttpRouteIntegration {
40+
export abstract class HttpPrivateIntegration extends HttpRouteIntegration {
4141
protected httpMethod = HttpMethod.ANY;
4242
protected payloadFormatVersion = PayloadFormatVersion.VERSION_1_0; // 1.0 is required and is the only supported format
4343
protected integrationType = HttpIntegrationType.HTTP_PROXY;

‎packages/@aws-cdk/aws-apigatewayv2-integrations/lib/websocket/lambda.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
IWebSocketRouteIntegration,
2+
WebSocketRouteIntegration,
33
WebSocketIntegrationType,
44
WebSocketRouteIntegrationBindOptions,
55
WebSocketRouteIntegrationConfig,
@@ -21,8 +21,10 @@ export interface LambdaWebSocketIntegrationProps {
2121
/**
2222
* Lambda WebSocket Integration
2323
*/
24-
export class LambdaWebSocketIntegration implements IWebSocketRouteIntegration {
25-
constructor(private props: LambdaWebSocketIntegrationProps) {}
24+
export class LambdaWebSocketIntegration extends WebSocketRouteIntegration {
25+
constructor(private props: LambdaWebSocketIntegrationProps) {
26+
super();
27+
}
2628

2729
bind(options: WebSocketRouteIntegrationBindOptions): WebSocketRouteIntegrationConfig {
2830
const route = options.route;

‎packages/@aws-cdk/aws-apigatewayv2/lib/common/base.ts

-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as cloudwatch from '@aws-cdk/aws-cloudwatch';
22
import { Resource } from '@aws-cdk/core';
3-
import { IntegrationCache } from '../private/integration-cache';
43
import { IApi } from './api';
54
import { ApiMapping } from './api-mapping';
65
import { DomainMappingOptions, IStage } from './stage';
@@ -12,10 +11,6 @@ import { DomainMappingOptions, IStage } from './stage';
1211
export abstract class ApiBase extends Resource implements IApi {
1312
abstract readonly apiId: string;
1413
abstract readonly apiEndpoint: string;
15-
/**
16-
* @internal
17-
*/
18-
protected _integrationCache: IntegrationCache = new IntegrationCache();
1914

2015
public metric(metricName: string, props?: cloudwatch.MetricOptions): cloudwatch.Metric {
2116
return new cloudwatch.Metric({

‎packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts

+2-33
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { IApi } from '../common/api';
66
import { ApiBase } from '../common/base';
77
import { DomainMappingOptions } from '../common/stage';
88
import { IHttpRouteAuthorizer } from './authorizer';
9-
import { IHttpRouteIntegration, HttpIntegration, HttpRouteIntegrationConfig } from './integration';
9+
import { HttpRouteIntegration } from './integration';
1010
import { BatchHttpRouteOptions, HttpMethod, HttpRoute, HttpRouteKey } from './route';
1111
import { IHttpStage, HttpStage, HttpStageOptions } from './stage';
1212
import { VpcLink, VpcLinkProps } from './vpc-link';
@@ -71,12 +71,6 @@ export interface IHttpApi extends IApi {
7171
* Add a new VpcLink
7272
*/
7373
addVpcLink(options: VpcLinkProps): VpcLink
74-
75-
/**
76-
* Add a http integration
77-
* @internal
78-
*/
79-
_addIntegration(scope: Construct, config: HttpRouteIntegrationConfig): HttpIntegration;
8074
}
8175

8276
/**
@@ -99,7 +93,7 @@ export interface HttpApiProps {
9993
* An integration that will be configured on the catch-all route ($default).
10094
* @default - none
10195
*/
102-
readonly defaultIntegration?: IHttpRouteIntegration;
96+
readonly defaultIntegration?: HttpRouteIntegration;
10397

10498
/**
10599
* Whether a default stage and deployment should be automatically created.
@@ -286,31 +280,6 @@ abstract class HttpApiBase extends ApiBase implements IHttpApi { // note that th
286280

287281
return vpcLink;
288282
}
289-
290-
/**
291-
* @internal
292-
*/
293-
public _addIntegration(scope: Construct, config: HttpRouteIntegrationConfig): HttpIntegration {
294-
const { configHash, integration: existingIntegration } = this._integrationCache.getIntegration(scope, config);
295-
if (existingIntegration) {
296-
return existingIntegration as HttpIntegration;
297-
}
298-
299-
const integration = new HttpIntegration(scope, `HttpIntegration-${configHash}`, {
300-
httpApi: this,
301-
integrationType: config.type,
302-
integrationUri: config.uri,
303-
method: config.method,
304-
connectionId: config.connectionId,
305-
connectionType: config.connectionType,
306-
payloadFormatVersion: config.payloadFormatVersion,
307-
secureServerName: config.secureServerName,
308-
parameterMapping: config.parameterMapping,
309-
});
310-
this._integrationCache.saveIntegration(scope, config, integration);
311-
312-
return integration;
313-
}
314283
}
315284

316285
/**

‎packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts

+39-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable quotes */
2-
import { Resource } from '@aws-cdk/core';
2+
import * as crypto from 'crypto';
3+
import { Resource, Stack } from '@aws-cdk/core';
34
import { Construct } from 'constructs';
45
import { CfnIntegration } from '../apigatewayv2.generated';
56
import { IIntegration } from '../common';
@@ -191,11 +192,46 @@ export interface HttpRouteIntegrationBindOptions {
191192
/**
192193
* The interface that various route integration classes will inherit.
193194
*/
194-
export interface IHttpRouteIntegration {
195+
export abstract class HttpRouteIntegration {
196+
private integration?: HttpIntegration;
197+
198+
/**
199+
* Internal method called when binding this integration to the route.
200+
* @internal
201+
*/
202+
public _bindToRoute(options: HttpRouteIntegrationBindOptions): { readonly integrationId: string } {
203+
if (this.integration && this.integration.httpApi.node.addr !== options.route.httpApi.node.addr) {
204+
throw new Error('A single integration cannot be associated with multiple APIs.');
205+
}
206+
207+
if (!this.integration) {
208+
const config = this.bind(options);
209+
210+
this.integration = new HttpIntegration(options.scope, `HttpIntegration-${hash(config)}`, {
211+
httpApi: options.route.httpApi,
212+
integrationType: config.type,
213+
integrationUri: config.uri,
214+
method: config.method,
215+
connectionId: config.connectionId,
216+
connectionType: config.connectionType,
217+
payloadFormatVersion: config.payloadFormatVersion,
218+
secureServerName: config.secureServerName,
219+
parameterMapping: config.parameterMapping,
220+
});
221+
222+
function hash(x: any) {
223+
const stringifiedConfig = JSON.stringify(Stack.of(options.scope).resolve(x));
224+
const configHash = crypto.createHash('md5').update(stringifiedConfig).digest('hex');
225+
return configHash;
226+
}
227+
}
228+
return { integrationId: this.integration.integrationId };
229+
}
230+
195231
/**
196232
* Bind this integration to the route.
197233
*/
198-
bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig;
234+
public abstract bind(options: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig;
199235
}
200236

201237
/**

‎packages/@aws-cdk/aws-apigatewayv2/lib/http/route.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { CfnRoute, CfnRouteProps } from '../apigatewayv2.generated';
44
import { IRoute } from '../common';
55
import { IHttpApi } from './api';
66
import { IHttpRouteAuthorizer } from './authorizer';
7-
import { IHttpRouteIntegration } from './integration';
7+
import { HttpRouteIntegration } from './integration';
88

99
/**
1010
* Represents a Route for an HTTP API.
@@ -88,7 +88,7 @@ export interface BatchHttpRouteOptions {
8888
/**
8989
* The integration to be configured on this route.
9090
*/
91-
readonly integration: IHttpRouteIntegration;
91+
readonly integration: HttpRouteIntegration;
9292
}
9393

9494
/**
@@ -149,13 +149,11 @@ export class HttpRoute extends Resource implements IHttpRoute {
149149
this.httpApi = props.httpApi;
150150
this.path = props.routeKey.path;
151151

152-
const config = props.integration.bind({
152+
const config = props.integration._bindToRoute({
153153
route: this,
154154
scope: this,
155155
});
156156

157-
const integration = props.httpApi._addIntegration(this, config);
158-
159157
const authBindResult = props.authorizer ? props.authorizer.bind({
160158
route: this,
161159
scope: this.httpApi instanceof Construct ? this.httpApi : this, // scope under the API if it's not imported
@@ -181,7 +179,7 @@ export class HttpRoute extends Resource implements IHttpRoute {
181179
const routeProps: CfnRouteProps = {
182180
apiId: props.httpApi.apiId,
183181
routeKey: props.routeKey.key,
184-
target: `integrations/${integration.integrationId}`,
182+
target: `integrations/${config.integrationId}`,
185183
authorizerId: authBindResult?.authorizerId,
186184
authorizationType: authBindResult?.authorizationType ?? 'NONE', // must be explicitly NONE (not undefined) for stack updates to work correctly
187185
authorizationScopes,

‎packages/@aws-cdk/aws-apigatewayv2/lib/private/integration-cache.ts

-29
This file was deleted.

‎packages/@aws-cdk/aws-apigatewayv2/lib/websocket/api.ts

-25
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,12 @@ import { Construct } from 'constructs';
44
import { CfnApi } from '../apigatewayv2.generated';
55
import { IApi } from '../common/api';
66
import { ApiBase } from '../common/base';
7-
import { WebSocketRouteIntegrationConfig, WebSocketIntegration } from './integration';
87
import { WebSocketRoute, WebSocketRouteOptions } from './route';
98

109
/**
1110
* Represents a WebSocket API
1211
*/
1312
export interface IWebSocketApi extends IApi {
14-
/**
15-
* Add a websocket integration
16-
* @internal
17-
*/
18-
_addIntegration(scope: Construct, config: WebSocketRouteIntegrationConfig): WebSocketIntegration
1913
}
2014

2115
/**
@@ -100,25 +94,6 @@ export class WebSocketApi extends ApiBase implements IWebSocketApi {
10094
}
10195
}
10296

103-
/**
104-
* @internal
105-
*/
106-
public _addIntegration(scope: Construct, config: WebSocketRouteIntegrationConfig): WebSocketIntegration {
107-
const { configHash, integration: existingIntegration } = this._integrationCache.getIntegration(scope, config);
108-
if (existingIntegration) {
109-
return existingIntegration as WebSocketIntegration;
110-
}
111-
112-
const integration = new WebSocketIntegration(scope, `WebSocketIntegration-${configHash}`, {
113-
webSocketApi: this,
114-
integrationType: config.type,
115-
integrationUri: config.uri,
116-
});
117-
this._integrationCache.saveIntegration(scope, config, integration);
118-
119-
return integration;
120-
}
121-
12297
/**
12398
* Add a new route
12499
*/

‎packages/@aws-cdk/aws-apigatewayv2/lib/websocket/integration.ts

+34-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Resource } from '@aws-cdk/core';
1+
import * as crypto from 'crypto';
2+
import { Resource, Stack } from '@aws-cdk/core';
23
import { Construct } from 'constructs';
34
import { CfnIntegration } from '../apigatewayv2.generated';
45
import { IIntegration } from '../common';
@@ -87,11 +88,41 @@ export interface WebSocketRouteIntegrationBindOptions {
8788
/**
8889
* The interface that various route integration classes will inherit.
8990
*/
90-
export interface IWebSocketRouteIntegration {
91+
export abstract class WebSocketRouteIntegration {
92+
private integration?: WebSocketIntegration;
93+
94+
/**
95+
* Internal method called when binding this integration to the route.
96+
* @internal
97+
*/
98+
public _bindToRoute(options: WebSocketRouteIntegrationBindOptions): { readonly integrationId: string } {
99+
if (this.integration && this.integration.webSocketApi.node.addr !== options.route.webSocketApi.node.addr) {
100+
throw new Error('A single integration cannot be associated with multiple APIs.');
101+
}
102+
103+
if (!this.integration) {
104+
const config = this.bind(options);
105+
106+
this.integration = new WebSocketIntegration(options.scope, `WebSocketIntegration-${hash(config)}`, {
107+
webSocketApi: options.route.webSocketApi,
108+
integrationType: config.type,
109+
integrationUri: config.uri,
110+
});
111+
112+
function hash(x: any) {
113+
const stringifiedConfig = JSON.stringify(Stack.of(options.scope).resolve(x));
114+
const configHash = crypto.createHash('md5').update(stringifiedConfig).digest('hex');
115+
return configHash;
116+
}
117+
}
118+
119+
return { integrationId: this.integration.integrationId };
120+
}
121+
91122
/**
92123
* Bind this integration to the route.
93124
*/
94-
bind(options: WebSocketRouteIntegrationBindOptions): WebSocketRouteIntegrationConfig;
125+
public abstract bind(options: WebSocketRouteIntegrationBindOptions): WebSocketRouteIntegrationConfig;
95126
}
96127

97128
/**

‎packages/@aws-cdk/aws-apigatewayv2/lib/websocket/route.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Construct } from 'constructs';
33
import { CfnRoute } from '../apigatewayv2.generated';
44
import { IRoute } from '../common';
55
import { IWebSocketApi } from './api';
6-
import { IWebSocketRouteIntegration } from './integration';
6+
import { WebSocketRouteIntegration } from './integration';
77

88
/**
99
* Represents a Route for an WebSocket API.
@@ -28,7 +28,7 @@ export interface WebSocketRouteOptions {
2828
/**
2929
* The integration to be configured on this route.
3030
*/
31-
readonly integration: IWebSocketRouteIntegration;
31+
readonly integration: WebSocketRouteIntegration;
3232
}
3333

3434

@@ -67,17 +67,15 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute {
6767
this.webSocketApi = props.webSocketApi;
6868
this.routeKey = props.routeKey;
6969

70-
const config = props.integration.bind({
70+
const config = props.integration._bindToRoute({
7171
route: this,
7272
scope: this,
7373
});
7474

75-
const integration = props.webSocketApi._addIntegration(this, config);
76-
7775
const route = new CfnRoute(this, 'Resource', {
7876
apiId: props.webSocketApi.apiId,
7977
routeKey: props.routeKey,
80-
target: `integrations/${integration.integrationId}`,
78+
target: `integrations/${config.integrationId}`,
8179
});
8280
this.routeId = route.ref;
8381
}

‎packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Duration, Stack } from '@aws-cdk/core';
66
import {
77
CorsHttpMethod, DomainName,
88
HttpApi, HttpAuthorizer, HttpIntegrationType, HttpMethod, HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig,
9-
HttpRouteIntegrationBindOptions, HttpRouteIntegrationConfig, IHttpRouteAuthorizer, IHttpRouteIntegration, HttpNoneAuthorizer, PayloadFormatVersion,
9+
HttpRouteIntegrationBindOptions, HttpRouteIntegrationConfig, IHttpRouteAuthorizer, HttpRouteIntegration, HttpNoneAuthorizer, PayloadFormatVersion,
1010
} from '../../lib';
1111

1212
describe('HttpApi', () => {
@@ -531,7 +531,7 @@ describe('HttpApi', () => {
531531
});
532532
});
533533

534-
class DummyRouteIntegration implements IHttpRouteIntegration {
534+
class DummyRouteIntegration extends HttpRouteIntegration {
535535
public bind(_: HttpRouteIntegrationBindOptions): HttpRouteIntegrationConfig {
536536
return {
537537
payloadFormatVersion: PayloadFormatVersion.VERSION_2_0,

‎packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts

+24-24
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Template } from '@aws-cdk/assertions';
22
import { Stack, App } from '@aws-cdk/core';
33
import {
44
HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpConnectionType, HttpIntegrationType, HttpMethod, HttpRoute,
5-
HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteAuthorizer, IHttpRouteIntegration,
5+
HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteAuthorizer, HttpRouteIntegration,
66
MappingValue,
77
ParameterMapping,
88
PayloadFormatVersion,
@@ -81,42 +81,42 @@ describe('HttpRoute', () => {
8181
Template.fromStack(stack).resourceCountIs('AWS::ApiGatewayV2::Integration', 1);
8282
});
8383

84-
test('integration can be used across HttpApis', () => {
84+
test('integration cannot be used across HttpApis', () => {
8585
// GIVEN
8686
const integration = new DummyIntegration();
8787

8888
// WHEN
89-
const stack1 = new Stack();
90-
const httpApi1 = new HttpApi(stack1, 'HttpApi1');
89+
const stack = new Stack();
90+
const httpApi1 = new HttpApi(stack, 'HttpApi1');
91+
const httpApi2 = new HttpApi(stack, 'HttpApi2');
9192

92-
new HttpRoute(stack1, 'HttpRoute1', {
93+
new HttpRoute(stack, 'HttpRoute1', {
9394
httpApi: httpApi1,
9495
integration,
9596
routeKey: HttpRouteKey.with('/books', HttpMethod.GET),
9697
});
97-
new HttpRoute(stack1, 'HttpRoute2', {
98-
httpApi: httpApi1,
99-
integration,
100-
routeKey: HttpRouteKey.with('/books', HttpMethod.POST),
101-
});
102-
103-
const stack2 = new Stack();
104-
const httpApi2 = new HttpApi(stack2, 'HttpApi2');
10598

106-
new HttpRoute(stack2, 'HttpRoute1', {
99+
expect(() => new HttpRoute(stack, 'HttpRoute2', {
107100
httpApi: httpApi2,
108101
integration,
109102
routeKey: HttpRouteKey.with('/books', HttpMethod.GET),
103+
})).toThrow(/cannot be associated with multiple APIs/);
104+
});
105+
106+
test('associating integrations in different APIs creates separate AWS::ApiGatewayV2::Integration', () => {
107+
const stack = new Stack();
108+
109+
const api = new HttpApi(stack, 'HttpApi');
110+
api.addRoutes({
111+
path: '/books',
112+
integration: new DummyIntegration(),
110113
});
111-
new HttpRoute(stack2, 'HttpRoute2', {
112-
httpApi: httpApi2,
113-
integration,
114-
routeKey: HttpRouteKey.with('/books', HttpMethod.POST),
114+
api.addRoutes({
115+
path: '/magazines',
116+
integration: new DummyIntegration(),
115117
});
116118

117-
// THEN
118-
Template.fromStack(stack1).resourceCountIs('AWS::ApiGatewayV2::Integration', 1);
119-
Template.fromStack(stack2).resourceCountIs('AWS::ApiGatewayV2::Integration', 1);
119+
Template.fromStack(stack).hasResource('AWS::ApiGatewayV2::Integration', 2);
120120
});
121121

122122
test('route defined in a separate stack does not create cycles', () => {
@@ -167,7 +167,7 @@ describe('HttpRoute', () => {
167167
const stack = new Stack();
168168
const httpApi = new HttpApi(stack, 'HttpApi');
169169

170-
class PrivateIntegration implements IHttpRouteIntegration {
170+
class PrivateIntegration extends HttpRouteIntegration {
171171
public bind(): HttpRouteIntegrationConfig {
172172
return {
173173
method: HttpMethod.ANY,
@@ -212,7 +212,7 @@ describe('HttpRoute', () => {
212212
const stack = new Stack();
213213
const httpApi = new HttpApi(stack, 'HttpApi');
214214

215-
class PrivateIntegration implements IHttpRouteIntegration {
215+
class PrivateIntegration extends HttpRouteIntegration {
216216
public bind(): HttpRouteIntegrationConfig {
217217
return {
218218
method: HttpMethod.ANY,
@@ -310,7 +310,7 @@ describe('HttpRoute', () => {
310310
});
311311
});
312312

313-
class DummyIntegration implements IHttpRouteIntegration {
313+
class DummyIntegration extends HttpRouteIntegration {
314314
public bind(): HttpRouteIntegrationConfig {
315315
return {
316316
type: HttpIntegrationType.HTTP_PROXY,

‎packages/@aws-cdk/aws-apigatewayv2/test/websocket/api.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Match, Template } from '@aws-cdk/assertions';
22
import { User } from '@aws-cdk/aws-iam';
33
import { Stack } from '@aws-cdk/core';
44
import {
5-
IWebSocketRouteIntegration, WebSocketApi, WebSocketIntegrationType,
5+
WebSocketRouteIntegration, WebSocketApi, WebSocketIntegrationType,
66
WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig,
77
} from '../../lib';
88

@@ -126,7 +126,7 @@ describe('WebSocketApi', () => {
126126
});
127127
});
128128

129-
class DummyIntegration implements IWebSocketRouteIntegration {
129+
class DummyIntegration extends WebSocketRouteIntegration {
130130
bind(_options: WebSocketRouteIntegrationBindOptions): WebSocketRouteIntegrationConfig {
131131
return {
132132
type: WebSocketIntegrationType.AWS_PROXY,

‎packages/@aws-cdk/aws-apigatewayv2/test/websocket/route.test.ts

+42-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Template } from '@aws-cdk/assertions';
22
import { Stack } from '@aws-cdk/core';
33
import {
4-
IWebSocketRouteIntegration, WebSocketApi, WebSocketIntegrationType,
4+
WebSocketRouteIntegration, WebSocketApi, WebSocketIntegrationType,
55
WebSocketRoute, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig,
66
} from '../../lib';
77

@@ -41,10 +41,50 @@ describe('WebSocketRoute', () => {
4141
IntegrationUri: 'some-uri',
4242
});
4343
});
44+
45+
test('integration cannot be used across WebSocketApis', () => {
46+
// GIVEN
47+
const integration = new DummyIntegration();
48+
49+
// WHEN
50+
const stack = new Stack();
51+
const webSocketApi1 = new WebSocketApi(stack, 'WebSocketApi1');
52+
const webSocketApi2 = new WebSocketApi(stack, 'WebSocketApi2');
53+
54+
new WebSocketRoute(stack, 'WebSocketRoute1', {
55+
webSocketApi: webSocketApi1,
56+
integration,
57+
routeKey: 'route',
58+
});
59+
60+
expect(() => new WebSocketRoute(stack, 'WebSocketRoute2', {
61+
webSocketApi: webSocketApi2,
62+
integration,
63+
routeKey: 'route',
64+
})).toThrow(/cannot be associated with multiple APIs/);
65+
});
66+
67+
test('associating integrations in different APIs creates separate AWS::ApiGatewayV2::Integration', () => {
68+
const stack = new Stack();
69+
70+
const api = new WebSocketApi(stack, 'WebSocketApi');
71+
new WebSocketRoute(stack, 'WebSocketRoute1', {
72+
webSocketApi: api,
73+
integration: new DummyIntegration(),
74+
routeKey: '/books',
75+
});
76+
new WebSocketRoute(stack, 'WebSocketRoute2', {
77+
webSocketApi: api,
78+
integration: new DummyIntegration(),
79+
routeKey: '/magazines',
80+
});
81+
82+
Template.fromStack(stack).hasResource('AWS::ApiGatewayV2::Integration', 2);
83+
});
4484
});
4585

4686

47-
class DummyIntegration implements IWebSocketRouteIntegration {
87+
class DummyIntegration extends WebSocketRouteIntegration {
4888
bind(_options: WebSocketRouteIntegrationBindOptions): WebSocketRouteIntegrationConfig {
4989
return {
5090
type: WebSocketIntegrationType.AWS_PROXY,

0 commit comments

Comments
 (0)
Please sign in to comment.