From 8e020be1c7af3ab0170dde8544b275f79c831069 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Fri, 29 Mar 2024 17:16:23 +0100 Subject: [PATCH 01/39] feat: response class (wip) --- .../aws-apigatewayv2/lib/websocket/index.ts | 1 + .../lib/websocket/integration-response.ts | 179 ++++++++++++++++++ .../lib/websocket/integration.ts | 9 + 3 files changed, 189 insertions(+) create mode 100644 packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/index.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/index.ts index 4fe65943cbb8b..68cfe69160291 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/index.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/index.ts @@ -2,4 +2,5 @@ export * from './api'; export * from './route'; export * from './stage'; export * from './integration'; +export * from './integration-response'; export * from './authorizer'; diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts new file mode 100644 index 0000000000000..b07db472f8a87 --- /dev/null +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -0,0 +1,179 @@ +import { Construct } from 'constructs'; +import { ContentHandling, IWebSocketIntegration } from './integration'; +import { IResource, Resource } from '../../../core'; +import { CfnIntegrationResponse } from '../apigatewayv2.generated'; + +/** + * WebSocket integration response key helper class + */ +export class WebSocketIntegrationResponseKey { + /** + * Generate an integration response key from an HTTP status code + * + * @example + * // Match 403 status code + * WebSocketIntegrationResponseKey.fromStatusCode(403) + * + * @param httpStatusCode HTTP status code of the mapped response + */ + public static fromStatusCode(httpStatusCode: number): WebSocketIntegrationResponseKey { + return new WebSocketIntegrationResponseKey(`/${httpStatusCode}/`); + + } + + /** + * Generate an integration response key from a regular expression matching HTTP status codes + * + * @example + * // Match all 20x status codes + * WebSocketIntegrationResponseKey.fromStatusRegExp('/20\d/') + * + * @param httpStatusRegExpStr HTTP status code regular expression string representation + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp + */ + public static fromStatusRegExp(httpStatusRegExpStr: string): WebSocketIntegrationResponseKey { + const httpStatusRegExp = new RegExp(httpStatusRegExpStr); + + if (httpStatusRegExp.flags) { + throw new Error( + `RegExp flags are not supported, got '${httpStatusRegExp}', expected '/${httpStatusRegExp.source}/'`, + ); + } + + return new WebSocketIntegrationResponseKey(`/${httpStatusRegExp.source}/`); + } + + /** + * Match all responses + */ + public static get default(): WebSocketIntegrationResponseKey { + return new WebSocketIntegrationResponseKey('$default'); + } + + /** + * Match all 2xx responses (HTTP success codes) + */ + public static get success(): WebSocketIntegrationResponseKey { + return WebSocketIntegrationResponseKey.fromStatusRegExp('/2\d{2}/'); + } + + /** + * Match all 4xx responses (HTTP client error codes) + */ + public static get clientError(): WebSocketIntegrationResponseKey { + return WebSocketIntegrationResponseKey.fromStatusRegExp('/4\d{2}/'); + } + + /** + * Match all 5xx responses (HTTP server error codes) + */ + public static get serverError(): WebSocketIntegrationResponseKey { + return WebSocketIntegrationResponseKey.fromStatusRegExp('/5\d{2}/'); + } + + private constructor(readonly key: string) {} +} + +/** + * WebSocket integration response properties + * + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html + */ +export interface WebSocketIntegrationResponseProps { + /** + * The HTTP status code the response will be mapped to + */ + readonly responseKey: WebSocketIntegrationResponseKey; + + /** + * TODO + * @default - No response templates + */ + readonly responseTemplates?: { [contentType: string]: string }; + + /** + * Specifies how to handle response payload content type conversions. + * + * @default - The response payload will be passed through from the integration response to + * the route response or method response without modification. + */ + readonly contentHandling?: ContentHandling; + + /** + * TODO + * @default - No response parameters + */ + readonly responseParameters?: { [key: string]: string }; + + /** + * The template selection expression for the integration response. + * + * @default - No template selection expression + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-data-transformations.html#apigateway-websocket-api-integration-response-selection-expressions + */ + readonly templateSelectionExpression?: string; +} + +/** + * Represents an Integration Response for an WebSocket API. + */ +export interface IWebSocketIntegrationResponse extends IResource { + /** + * The integration the response will be mapped to + */ + readonly integration: IWebSocketIntegration; + + /** + * The integration response key. + */ + readonly responseKey: WebSocketIntegrationResponseKey; +} + +/** + * WebSocket Integration Response resource class + */ +export class WebSocketIntegrationResponse extends Resource implements IWebSocketIntegrationResponse { + /** + * Generate an array of WebSocket Integration Response resources from a map + * and associate them with a given WebSocket Integration + * + * @param scope The parent construct + * @param integration The WebSocket Integration to associate the responses with + * @param responsesProps The array of properties to create WebSocket Integration Responses from + */ + public static fromIntegrationResponseMap( + scope: Construct, + integration: IWebSocketIntegration, + responsesProps: WebSocketIntegrationResponseProps[], + ): WebSocketIntegrationResponse[] { + return responsesProps.map((responseProps) => + new WebSocketIntegrationResponse(scope, integration, responseProps), + ); + } + + /** + * The integration response key. + */ + readonly responseKey: WebSocketIntegrationResponseKey; + + private constructor( + scope: Construct, + readonly integration: IWebSocketIntegration, + props: WebSocketIntegrationResponseProps, + ) { + // TODO generate a unique id from integration id + key + super(scope, '1234'); + + new CfnIntegrationResponse(this, 'Resource', { + apiId: this.integration.webSocketApi.apiId, + integrationId: this.integration.integrationId, + integrationResponseKey: props.responseKey.key, + responseTemplates: props.responseTemplates, + contentHandlingStrategy: props.contentHandling, + responseParameters: props.responseParameters, + templateSelectionExpression: props.templateSelectionExpression, + }); + + this.responseKey = props.responseKey; + } +} diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts index 2485f7368a5bc..a3c686276cec2 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts @@ -1,5 +1,6 @@ import { Construct } from 'constructs'; import { IWebSocketApi } from './api'; +import { WebSocketIntegrationResponse } from './integration-response'; import { IWebSocketRoute } from './route'; import { CfnIntegration } from '.././index'; import { IRole } from '../../../aws-iam'; @@ -303,6 +304,14 @@ export interface WebSocketRouteIntegrationConfig { */ readonly requestParameters?: { [dest: string]: string }; + /** + * Integration response configuration + * + * @default - No response configuration provided. + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html + */ + readonly responses?: WebSocketIntegrationResponse[]; + /** * Template selection expression * From c045d7150b0a7121e163eb536eff840958d1fd0d Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Fri, 29 Mar 2024 18:21:27 +0100 Subject: [PATCH 02/39] feat: response mapping, lambda unit test --- .../lib/websocket/aws.ts | 20 +++++ .../lib/websocket/lambda.ts | 19 +++++ .../test/websocket/aws.test.ts | 46 +++++++++- .../lib/websocket/integration-response.ts | 84 ++++++++----------- .../lib/websocket/integration.ts | 31 ++++++- 5 files changed, 148 insertions(+), 52 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts index dc265fb223b6c..bd82769b78bfb 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts @@ -5,6 +5,8 @@ import { WebSocketRouteIntegrationBindOptions, PassthroughBehavior, ContentHandling, + WebSocketIntegrationResponse, + WebSocketIntegrationResponseProps, } from '../../../aws-apigatewayv2'; import { IRole } from '../../../aws-iam'; import { Duration } from '../../../core'; @@ -59,6 +61,14 @@ export interface WebSocketAwsIntegrationProps { */ readonly requestTemplates?: { [contentType: string]: string }; + /** + * Integration responses configuration + * + * @default - No response configuration provided. + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html + */ + readonly responses?: WebSocketIntegrationResponseProps[]; + /** * The template selection expression for the integration. * @@ -106,9 +116,19 @@ export class WebSocketAwsIntegration extends WebSocketRouteIntegration { credentialsRole: this.props.credentialsRole, requestParameters: this.props.requestParameters, requestTemplates: this.props.requestTemplates, + responses: this.props.responses, passthroughBehavior: this.props.passthroughBehavior, templateSelectionExpression: this.props.templateSelectionExpression, timeout: this.props.timeout, }; } + + /** + * Add a response to this integration + * + * @param response The response to add + */ + addResponse(response: WebSocketIntegrationResponseProps) { + super.addResponse(response); + } } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts index 3ae153e63ec79..fd40dc3e3d2e1 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts @@ -4,6 +4,7 @@ import { WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, ContentHandling, + WebSocketIntegrationResponse, } from '../../../aws-apigatewayv2'; import { ServicePrincipal } from '../../../aws-iam'; import { IFunction } from '../../../aws-lambda'; @@ -28,6 +29,14 @@ export interface WebSocketLambdaIntegrationProps { * the route response or method response without modification. */ readonly contentHandling?: ContentHandling; + + /** + * Integration responses configuration + * + * @default - No response configuration provided. + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html + */ + readonly responses?: WebSocketIntegrationResponse[]; } /** @@ -75,6 +84,16 @@ export class WebSocketLambdaIntegration extends WebSocketRouteIntegration { uri: integrationUri, timeout: this.props.timeout, contentHandling: this.props.contentHandling, + responses: this.props.responses, }; } + + /** + * Add a response to this integration + * + * @param response The response to add + */ + addResponse(response: WebSocketIntegrationResponse) { + super.addResponse(response); + } } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/aws.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/aws.test.ts index 5294734854aba..8cef208436df5 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/aws.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/aws.test.ts @@ -1,6 +1,6 @@ import { WebSocketAwsIntegration } from './../../lib/websocket/aws'; import { Template } from '../../../assertions'; -import { ContentHandling, PassthroughBehavior, WebSocketApi } from '../../../aws-apigatewayv2'; +import { ContentHandling, PassthroughBehavior, WebSocketApi, WebSocketIntegrationResponseKey } from '../../../aws-apigatewayv2'; import * as iam from '../../../aws-iam'; import { Duration, Stack } from '../../../core'; @@ -47,6 +47,9 @@ describe('AwsWebSocketIntegration', () => { passthroughBehavior: PassthroughBehavior.WHEN_NO_TEMPLATES, contentHandling: ContentHandling.CONVERT_TO_BINARY, timeout: Duration.seconds(10), + responses: [ + { responseKey: WebSocketIntegrationResponseKey.success }, + ], }), }, }); @@ -66,5 +69,46 @@ describe('AwsWebSocketIntegration', () => { ContentHandlingStrategy: 'CONVERT_TO_BINARY', TimeoutInMillis: 10000, }); + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { + ApiId: { Ref: 'ApiF70053CD' }, + IntegrationId: { Ref: 'ApidefaultRouteAwsIntegration9C4CC31F' }, + IntegrationResponseKey: '/\\/2d{2}\\//', + }); + }); + + test('can add integration responses', () => { + // GIVEN + const stack = new Stack(); + const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); + const api = new WebSocketApi(stack, 'Api'); + + // THEN + const integration = new WebSocketAwsIntegration('AwsIntegration', { + integrationUri: 'arn:aws:apigateway:us-west-2:dynamodb:action/PutItem', + integrationMethod: 'POST', + responses: [ + { responseKey: WebSocketIntegrationResponseKey.default }, + ], + }); + integration.addResponse({ responseKey: WebSocketIntegrationResponseKey.clientError }); + + api.addRoute('$default', { integration }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'AWS', + IntegrationUri: 'arn:aws:apigateway:us-west-2:dynamodb:action/PutItem', + IntegrationMethod: 'POST', + }); + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { + ApiId: { Ref: 'ApiF70053CD' }, + IntegrationId: { Ref: 'ApidefaultRouteAwsIntegration9C4CC31F' }, + IntegrationResponseKey: '$default', + }); + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { + ApiId: { Ref: 'ApiF70053CD' }, + IntegrationId: { Ref: 'ApidefaultRouteAwsIntegration9C4CC31F' }, + IntegrationResponseKey: '/\\/4d{2}\\//', + }); }); }); diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index b07db472f8a87..e18592c12e389 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -1,12 +1,32 @@ import { Construct } from 'constructs'; import { ContentHandling, IWebSocketIntegration } from './integration'; -import { IResource, Resource } from '../../../core'; +import { IResource, Names, Resource } from '../../../core'; import { CfnIntegrationResponse } from '../apigatewayv2.generated'; /** * WebSocket integration response key helper class */ export class WebSocketIntegrationResponseKey { + /** + * Match all responses + */ + public static default= new WebSocketIntegrationResponseKey('$default'); + + /** + * Match all 2xx responses (HTTP success codes) + */ + public static success= WebSocketIntegrationResponseKey.fromStatusRegExp('/2\d{2}/'); + + /** + * Match all 4xx responses (HTTP client error codes) + */ + public static clientError= WebSocketIntegrationResponseKey.fromStatusRegExp('/4\d{2}/'); + + /** + * Match all 5xx responses (HTTP server error codes) + */ + public static serverError= WebSocketIntegrationResponseKey.fromStatusRegExp('/5\d{2}/'); + /** * Generate an integration response key from an HTTP status code * @@ -43,34 +63,6 @@ export class WebSocketIntegrationResponseKey { return new WebSocketIntegrationResponseKey(`/${httpStatusRegExp.source}/`); } - /** - * Match all responses - */ - public static get default(): WebSocketIntegrationResponseKey { - return new WebSocketIntegrationResponseKey('$default'); - } - - /** - * Match all 2xx responses (HTTP success codes) - */ - public static get success(): WebSocketIntegrationResponseKey { - return WebSocketIntegrationResponseKey.fromStatusRegExp('/2\d{2}/'); - } - - /** - * Match all 4xx responses (HTTP client error codes) - */ - public static get clientError(): WebSocketIntegrationResponseKey { - return WebSocketIntegrationResponseKey.fromStatusRegExp('/4\d{2}/'); - } - - /** - * Match all 5xx responses (HTTP server error codes) - */ - public static get serverError(): WebSocketIntegrationResponseKey { - return WebSocketIntegrationResponseKey.fromStatusRegExp('/5\d{2}/'); - } - private constructor(readonly key: string) {} } @@ -133,36 +125,28 @@ export interface IWebSocketIntegrationResponse extends IResource { * WebSocket Integration Response resource class */ export class WebSocketIntegrationResponse extends Resource implements IWebSocketIntegrationResponse { + /** + * The integration response key. + */ + readonly responseKey: WebSocketIntegrationResponseKey; + /** * Generate an array of WebSocket Integration Response resources from a map * and associate them with a given WebSocket Integration * * @param scope The parent construct * @param integration The WebSocket Integration to associate the responses with - * @param responsesProps The array of properties to create WebSocket Integration Responses from + * @param props The configuration properties to create WebSocket Integration Responses from */ - public static fromIntegrationResponseMap( - scope: Construct, - integration: IWebSocketIntegration, - responsesProps: WebSocketIntegrationResponseProps[], - ): WebSocketIntegrationResponse[] { - return responsesProps.map((responseProps) => - new WebSocketIntegrationResponse(scope, integration, responseProps), - ); - } - - /** - * The integration response key. - */ - readonly responseKey: WebSocketIntegrationResponseKey; - - private constructor( + constructor( scope: Construct, readonly integration: IWebSocketIntegration, props: WebSocketIntegrationResponseProps, ) { - // TODO generate a unique id from integration id + key - super(scope, '1234'); + super( + scope, + Names.nodeUniqueId(integration.node) + slugify(props.responseKey.key) + 'IntegrationResponse', + ); new CfnIntegrationResponse(this, 'Resource', { apiId: this.integration.webSocketApi.apiId, @@ -177,3 +161,7 @@ export class WebSocketIntegrationResponse extends Resource implements IWebSocket this.responseKey = props.responseKey; } } + +function slugify(x: string): string { + return x.replace(/[^a-zA-Z0-9]/g, ''); +} diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts index a3c686276cec2..a060fe5fad290 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts @@ -1,6 +1,6 @@ import { Construct } from 'constructs'; import { IWebSocketApi } from './api'; -import { WebSocketIntegrationResponse } from './integration-response'; +import { WebSocketIntegrationResponse, WebSocketIntegrationResponseProps } from './integration-response'; import { IWebSocketRoute } from './route'; import { CfnIntegration } from '.././index'; import { IRole } from '../../../aws-iam'; @@ -211,6 +211,7 @@ export interface WebSocketRouteIntegrationBindOptions { */ export abstract class WebSocketRouteIntegration { private integration?: WebSocketIntegration; + private responses: WebSocketIntegrationResponseProps[] = []; /** * Initialize an integration for a route on websocket api. @@ -243,11 +244,35 @@ export abstract class WebSocketRouteIntegration { passthroughBehavior: config.passthroughBehavior, templateSelectionExpression: config.templateSelectionExpression, }); + + this.responses.push(...config.responses ?? []); + this.responses.reduce<{ [key: string]: string }>((acc, props) => { + if (props.responseKey.key in acc) { + throw new Error(`Duplicate integration response key: "${props.responseKey.key}"`); + } + + const key = props.responseKey.key; + acc[key] = props.responseKey.key; + return acc; + }, {}); + + for (const response of this.responses) { + new WebSocketIntegrationResponse(options.scope, this.integration, response); + } } return { integrationId: this.integration.integrationId }; } + /** + * Add a response to this integration + * + * @param response The response to add + */ + protected addResponse(response: WebSocketIntegrationResponseProps) { + this.responses.push(response); + } + /** * Bind this integration to the route. */ @@ -305,12 +330,12 @@ export interface WebSocketRouteIntegrationConfig { readonly requestParameters?: { [dest: string]: string }; /** - * Integration response configuration + * Integration responses configuration * * @default - No response configuration provided. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html */ - readonly responses?: WebSocketIntegrationResponse[]; + readonly responses?: WebSocketIntegrationResponseProps[]; /** * Template selection expression From c5e81cd2b7b24bac94e8cea38895dfc6946db869 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Fri, 29 Mar 2024 19:46:05 +0100 Subject: [PATCH 03/39] chore: throws JSDoc --- .../aws-apigatewayv2/lib/websocket/integration-response.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index e18592c12e389..3a4713f14faa4 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -50,6 +50,8 @@ export class WebSocketIntegrationResponseKey { * * @param httpStatusRegExpStr HTTP status code regular expression string representation * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp + * + * @throws an error if {@link httpStatusRegExpStr} is not a valid regular expression string */ public static fromStatusRegExp(httpStatusRegExpStr: string): WebSocketIntegrationResponseKey { const httpStatusRegExp = new RegExp(httpStatusRegExpStr); From aa40b0feb9ecb996c88c53058e22317e95c74a39 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sun, 31 Mar 2024 11:10:37 +0200 Subject: [PATCH 04/39] chore: remove unused import --- .../aws-apigatewayv2-integrations/lib/websocket/aws.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts index bd82769b78bfb..bd20ff7b7c5dc 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts @@ -5,7 +5,6 @@ import { WebSocketRouteIntegrationBindOptions, PassthroughBehavior, ContentHandling, - WebSocketIntegrationResponse, WebSocketIntegrationResponseProps, } from '../../../aws-apigatewayv2'; import { IRole } from '../../../aws-iam'; From 25c948591cc9dc78194d338418d5a5f8f912294b Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sun, 31 Mar 2024 12:50:57 +0200 Subject: [PATCH 05/39] chore: unit tests --- .../lib/websocket/lambda.ts | 3 +- .../test/websocket/aws.test.ts | 42 +--- .../websocket/integration-response.test.ts | 237 ++++++++++++++++++ .../test/websocket/lambda.test.ts | 8 +- .../lib/websocket/integration-response.ts | 23 +- 5 files changed, 259 insertions(+), 54 deletions(-) create mode 100644 packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts index fd40dc3e3d2e1..d471fd70f4cb2 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts @@ -5,6 +5,7 @@ import { WebSocketRouteIntegrationConfig, ContentHandling, WebSocketIntegrationResponse, + WebSocketIntegrationResponseProps, } from '../../../aws-apigatewayv2'; import { ServicePrincipal } from '../../../aws-iam'; import { IFunction } from '../../../aws-lambda'; @@ -36,7 +37,7 @@ export interface WebSocketLambdaIntegrationProps { * @default - No response configuration provided. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html */ - readonly responses?: WebSocketIntegrationResponse[]; + readonly responses?: WebSocketIntegrationResponseProps[]; } /** diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/aws.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/aws.test.ts index 8cef208436df5..50d0013bc60b6 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/aws.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/aws.test.ts @@ -47,9 +47,7 @@ describe('AwsWebSocketIntegration', () => { passthroughBehavior: PassthroughBehavior.WHEN_NO_TEMPLATES, contentHandling: ContentHandling.CONVERT_TO_BINARY, timeout: Duration.seconds(10), - responses: [ - { responseKey: WebSocketIntegrationResponseKey.success }, - ], + responses: [{ responseKey: WebSocketIntegrationResponseKey.success }], }), }, }); @@ -72,43 +70,7 @@ describe('AwsWebSocketIntegration', () => { Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { ApiId: { Ref: 'ApiF70053CD' }, IntegrationId: { Ref: 'ApidefaultRouteAwsIntegration9C4CC31F' }, - IntegrationResponseKey: '/\\/2d{2}\\//', - }); - }); - - test('can add integration responses', () => { - // GIVEN - const stack = new Stack(); - const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); - const api = new WebSocketApi(stack, 'Api'); - - // THEN - const integration = new WebSocketAwsIntegration('AwsIntegration', { - integrationUri: 'arn:aws:apigateway:us-west-2:dynamodb:action/PutItem', - integrationMethod: 'POST', - responses: [ - { responseKey: WebSocketIntegrationResponseKey.default }, - ], - }); - integration.addResponse({ responseKey: WebSocketIntegrationResponseKey.clientError }); - - api.addRoute('$default', { integration }); - - // THEN - Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { - IntegrationType: 'AWS', - IntegrationUri: 'arn:aws:apigateway:us-west-2:dynamodb:action/PutItem', - IntegrationMethod: 'POST', - }); - Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { - ApiId: { Ref: 'ApiF70053CD' }, - IntegrationId: { Ref: 'ApidefaultRouteAwsIntegration9C4CC31F' }, - IntegrationResponseKey: '$default', - }); - Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { - ApiId: { Ref: 'ApiF70053CD' }, - IntegrationId: { Ref: 'ApidefaultRouteAwsIntegration9C4CC31F' }, - IntegrationResponseKey: '/\\/4d{2}\\//', + IntegrationResponseKey: '/2\\d{2}/', }); }); }); diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts new file mode 100644 index 0000000000000..8425de6cd3718 --- /dev/null +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts @@ -0,0 +1,237 @@ +import { Match, Template } from '../../../assertions'; +import { ContentHandling, WebSocketApi, WebSocketIntegrationResponseKey, WebSocketIntegrationResponseProps, WebSocketIntegrationType, WebSocketRouteIntegration, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig } from '../../../aws-apigatewayv2'; +import * as iam from '../../../aws-iam'; +import { Stack } from '../../../core'; + +interface WebSocketTestRouteIntegrationConfig { + /** + * Integration URI. + */ + readonly integrationUri: string; + + /** + * Integration responses configuration + * + * @default - No response configuration provided. + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html + */ + readonly responses?: WebSocketIntegrationResponseProps[]; +} + +class WebSocketTestIntegration extends WebSocketRouteIntegration { + constructor(id: string, private readonly props: WebSocketTestRouteIntegrationConfig) { + super(id); + } + + bind(_options: WebSocketRouteIntegrationBindOptions): WebSocketRouteIntegrationConfig { + return { + type: 'TEST' as WebSocketIntegrationType, + uri: this.props.integrationUri, + responses: this.props.responses, + }; + } + + /** + * Add a response to this integration + * + * @param response The response to add + */ + addResponse(response: WebSocketIntegrationResponseProps) { + super.addResponse(response); + } +} + +describe('WebSocketIntegrationResponseKey', () => { + test('constant response key values are correct', () => { + expect(WebSocketIntegrationResponseKey.default.key).toEqual('$default'); + expect(WebSocketIntegrationResponseKey.success.key).toEqual('/2\\d{2}/'); + expect(WebSocketIntegrationResponseKey.clientError.key).toEqual('/4\\d{2}/'); + expect(WebSocketIntegrationResponseKey.serverError.key).toEqual('/5\\d{2}/'); + }); + + test('can generate fromStatusCode', () => { + // GIVEN + const { key } = WebSocketIntegrationResponseKey.fromStatusCode(404); + + // THEN + expect(key).toEqual('/404/'); + }); + + test('can generate fromStatusRegExp', () => { + // GIVEN + const { key } = WebSocketIntegrationResponseKey.fromStatusRegExp(/4\d{2}/.source); + + // THEN + expect(key).toEqual('/4\\d{2}/'); + }); + + test('fromStatusRegExp throws if invalid RegExp', () => { + expect( + () => WebSocketIntegrationResponseKey.fromStatusRegExp('('), + ).toThrow(/Invalid regular expression/); + }); +}); + +describe('WebSocketIntegrationRespons', () => { + test('can set an integration response', () => { + // GIVEN + const stack = new Stack(); + const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); + const api = new WebSocketApi(stack, 'Api'); + + // THEN + const integration = new WebSocketTestIntegration('TestIntegration', { + integrationUri: 'https://example.com', + responses: [ + { responseKey: WebSocketIntegrationResponseKey.default }, + ], + }); + + api.addRoute('$default', { integration }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'TEST', + IntegrationUri: 'https://example.com', + }); + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { + ApiId: { Ref: 'ApiF70053CD' }, + IntegrationId: { Ref: 'ApidefaultRouteTestIntegration7AF569F1' }, + IntegrationResponseKey: '$default', + TemplateSelectionExpression: Match.absent(), + ContentHandling: Match.absent(), + ResponseParameters: Match.absent(), + ResponseTemplates: Match.absent(), + }); + }); + + test('can set custom integration response properties', () => { + // GIVEN + const stack = new Stack(); + const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); + const api = new WebSocketApi(stack, 'Api'); + + // THEN + const integration = new WebSocketTestIntegration('TestIntegration', { + integrationUri: 'https://example.com', + responses: [ + { + responseKey: WebSocketIntegrationResponseKey.fromStatusCode(404), + contentHandling: ContentHandling.CONVERT_TO_BINARY, + responseParameters: { + 'method.response.header.Accept': "'application/json'", + }, + templateSelectionExpression: '$default', + responseTemplates: { + 'application/json': '{ "message": $context.error.message, "statusCode": 404 }', + }, + }, + ], + }); + + api.addRoute('$default', { integration }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'TEST', + IntegrationUri: 'https://example.com', + }); + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { + ApiId: { Ref: 'ApiF70053CD' }, + IntegrationId: { Ref: 'ApidefaultRouteTestIntegration7AF569F1' }, + IntegrationResponseKey: '/404/', + ContentHandlingStrategy: 'CONVERT_TO_BINARY', + TemplateSelectionExpression: '$default', + ResponseParameters: { + 'method.response.header.Accept': "'application/json'", + }, + ResponseTemplates: { + 'application/json': '{ "message": $context.error.message, "statusCode": 404 }', + }, + }); + }); + + test('can add integration responses', () => { + // GIVEN + const stack = new Stack(); + const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); + const api = new WebSocketApi(stack, 'Api'); + + // THEN + const integration = new WebSocketTestIntegration('TestIntegration', { + integrationUri: 'https://example.com', + responses: [ + { responseKey: WebSocketIntegrationResponseKey.default }, + ], + }); + integration.addResponse({ responseKey: WebSocketIntegrationResponseKey.clientError }); + + api.addRoute('$default', { integration }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'TEST', + IntegrationUri: 'https://example.com', + }); + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { + ApiId: { Ref: 'ApiF70053CD' }, + IntegrationId: { Ref: 'ApidefaultRouteTestIntegration7AF569F1' }, + IntegrationResponseKey: '$default', + TemplateSelectionExpression: Match.absent(), + ContentHandling: Match.absent(), + ResponseParameters: Match.absent(), + ResponseTemplates: Match.absent(), + }); + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { + ApiId: { Ref: 'ApiF70053CD' }, + IntegrationId: { Ref: 'ApidefaultRouteTestIntegration7AF569F1' }, + IntegrationResponseKey: '/4\\d{2}/', + TemplateSelectionExpression: Match.absent(), + ContentHandling: Match.absent(), + ResponseParameters: Match.absent(), + ResponseTemplates: Match.absent(), + }); + }); + + test('throws if duplicate response key is set', () => { + // GIVEN + const stack = new Stack(); + const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); + const api = new WebSocketApi(stack, 'Api'); + + // THEN + const integration = new WebSocketTestIntegration('TestIntegration', { + integrationUri: 'https://example.com', + responses: [ + { responseKey: WebSocketIntegrationResponseKey.default }, + { responseKey: WebSocketIntegrationResponseKey.default }, + ], + }); + + // THEN + expect( + () => api.addRoute('$default', { integration }), + ).toThrow(/Duplicate integration response key/); + }); + + test('throws if duplicate response key is added', () => { + // GIVEN + const stack = new Stack(); + const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); + const api = new WebSocketApi(stack, 'Api'); + + // THEN + const integration = new WebSocketTestIntegration('TestIntegration', { + integrationUri: 'https://example.com', + responses: [ + { responseKey: WebSocketIntegrationResponseKey.default }, + ], + }); + integration.addResponse({ responseKey: WebSocketIntegrationResponseKey.default }); + + // THEN + expect( + () => api.addRoute('$default', { integration }), + ).toThrow(/Duplicate integration response key/); + }); +}); \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/lambda.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/lambda.test.ts index 8b3298ac425c1..c21a2d629cd1f 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/lambda.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/lambda.test.ts @@ -1,6 +1,6 @@ import { WebSocketLambdaIntegration } from './../../lib/websocket/lambda'; import { Template } from '../../../assertions'; -import { ContentHandling, WebSocketApi } from '../../../aws-apigatewayv2'; +import { ContentHandling, WebSocketApi, WebSocketIntegrationResponseKey } from '../../../aws-apigatewayv2'; import { Code, Function } from '../../../aws-lambda'; import * as lambda from '../../../aws-lambda'; import { Duration, Stack } from '../../../core'; @@ -63,6 +63,7 @@ describe('LambdaWebSocketIntegration', () => { { timeout: Duration.seconds(10), contentHandling: ContentHandling.CONVERT_TO_TEXT, + responses: [{ responseKey: WebSocketIntegrationResponseKey.success }], }, ), }, @@ -75,6 +76,11 @@ describe('LambdaWebSocketIntegration', () => { TimeoutInMillis: 10000, ContentHandlingStrategy: 'CONVERT_TO_TEXT', }); + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { + ApiId: { Ref: 'ApiF70053CD' }, + IntegrationId: { Ref: 'ApiconnectRouteIntegration5AB58E39' }, + IntegrationResponseKey: '/2\\d{2}/', + }); }); }); diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index 3a4713f14faa4..2a483771b8776 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -10,22 +10,22 @@ export class WebSocketIntegrationResponseKey { /** * Match all responses */ - public static default= new WebSocketIntegrationResponseKey('$default'); + public static default = new WebSocketIntegrationResponseKey('$default'); /** * Match all 2xx responses (HTTP success codes) */ - public static success= WebSocketIntegrationResponseKey.fromStatusRegExp('/2\d{2}/'); + public static success = WebSocketIntegrationResponseKey.fromStatusRegExp(/2\d{2}/.source); /** * Match all 4xx responses (HTTP client error codes) */ - public static clientError= WebSocketIntegrationResponseKey.fromStatusRegExp('/4\d{2}/'); + public static clientError = WebSocketIntegrationResponseKey.fromStatusRegExp(/4\d{2}/.source); /** * Match all 5xx responses (HTTP server error codes) */ - public static serverError= WebSocketIntegrationResponseKey.fromStatusRegExp('/5\d{2}/'); + public static serverError = WebSocketIntegrationResponseKey.fromStatusRegExp(/5\d{2}/.source); /** * Generate an integration response key from an HTTP status code @@ -46,7 +46,10 @@ export class WebSocketIntegrationResponseKey { * * @example * // Match all 20x status codes - * WebSocketIntegrationResponseKey.fromStatusRegExp('/20\d/') + * WebSocketIntegrationResponseKey.fromStatusRegExp('20\\d') + * + * // Match all 4xx status codes, using RegExp + * WebSocketIntegrationResponseKey.fromStatusRegExp(/4\d{2}/.source) * * @param httpStatusRegExpStr HTTP status code regular expression string representation * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp @@ -56,12 +59,6 @@ export class WebSocketIntegrationResponseKey { public static fromStatusRegExp(httpStatusRegExpStr: string): WebSocketIntegrationResponseKey { const httpStatusRegExp = new RegExp(httpStatusRegExpStr); - if (httpStatusRegExp.flags) { - throw new Error( - `RegExp flags are not supported, got '${httpStatusRegExp}', expected '/${httpStatusRegExp.source}/'`, - ); - } - return new WebSocketIntegrationResponseKey(`/${httpStatusRegExp.source}/`); } @@ -75,12 +72,13 @@ export class WebSocketIntegrationResponseKey { */ export interface WebSocketIntegrationResponseProps { /** - * The HTTP status code the response will be mapped to + * The HTTP status code or regular expression the response will be mapped to */ readonly responseKey: WebSocketIntegrationResponseKey; /** * TODO + * * @default - No response templates */ readonly responseTemplates?: { [contentType: string]: string }; @@ -95,6 +93,7 @@ export interface WebSocketIntegrationResponseProps { /** * TODO + * * @default - No response parameters */ readonly responseParameters?: { [key: string]: string }; From 7dc2af0b84447f4745da3de1f10c86c328d6323c Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sun, 31 Mar 2024 13:02:02 +0200 Subject: [PATCH 06/39] chore: README wip --- .../aws-apigatewayv2-integrations/README.md | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md index 724ac8bcd4a59..8ab4cfc13903e 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md @@ -10,6 +10,7 @@ - [WebSocket APIs](#websocket-apis) - [Lambda WebSocket Integration](#lambda-websocket-integration) - [AWS WebSocket Integration](#aws-websocket-integration) + - [Integration Responses](#integration-responses) ## HTTP APIs @@ -306,4 +307,46 @@ webSocketApi.addRoute('$connect', { ``` You can also set additional properties to change the behavior of your integration, such as `contentHandling`. -See [Working with binary media types for WebSocket APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-develop-binary-media-types.html). \ No newline at end of file +See [Working with binary media types for WebSocket APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/websocket-api-develop-binary-media-types.html). + + +### Integration Responses + +```ts +import * as integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations'; + +const webSocketApi = new apigwv2.WebSocketApi(this, 'mywsapi'); +new apigwv2.WebSocketStage(this, 'mystage', { + webSocketApi, + stageName: 'dev', + autoDeploy: true, +}); + +declare const messageHandler: lambda.Function; +const integration = new integrations.WebSocketLambdaIntegration( + 'SendMessageIntegration', + messageHandler, + { + responses: [ + // Default response key, will be used if no other matched + { responseKey: integrations.WebSocketIntegrationResponseKey.default }, + // Success response key, will match all 2xx response HTTP status codes + { responseKey: integrations.WebSocketIntegrationResponseKey.success }, + // You can also create custom response integrations for specific status codes + { + responseKey: integrations.WebSocketIntegrationResponseKey.fromStatusCode(404), + contentHandling: ContentHandling.CONVERT_TO_BINARY, + responseParameters: { + 'method.response.header.Accept': "'application/json'", + }, + templateSelectionExpression: '$default', + responseTemplates: { + 'application/json': '{ "message": $context.error.message, "statusCode": 404 }', + }, + }, + ], + }, +); + +webSocketApi.addRoute('sendMessage', { integration }); +``` \ No newline at end of file From 664e1b2aa5eb906bd7d9ca125e1a1b3c7111aa02 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sun, 31 Mar 2024 14:48:27 +0200 Subject: [PATCH 07/39] chore: add returnResponse check, refactor WebSocketTwoWayRouteIntegration --- .../lib/websocket/aws.ts | 13 +-- .../lib/websocket/lambda.ts | 12 +-- .../websocket/integration-response.test.ts | 34 +++++-- .../lib/websocket/integration-response.ts | 17 +++- .../lib/websocket/integration.ts | 88 ++++++++++++++----- .../aws-apigatewayv2/lib/websocket/route.ts | 1 + 6 files changed, 113 insertions(+), 52 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts index bd20ff7b7c5dc..8d286f4ba0cfb 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts @@ -1,11 +1,11 @@ import { - WebSocketRouteIntegration, WebSocketIntegrationType, WebSocketRouteIntegrationConfig, WebSocketRouteIntegrationBindOptions, PassthroughBehavior, ContentHandling, WebSocketIntegrationResponseProps, + WebSocketTwoWayRouteIntegration, } from '../../../aws-apigatewayv2'; import { IRole } from '../../../aws-iam'; import { Duration } from '../../../core'; @@ -98,7 +98,7 @@ export interface WebSocketAwsIntegrationProps { /** * AWS WebSocket AWS Type Integration */ -export class WebSocketAwsIntegration extends WebSocketRouteIntegration { +export class WebSocketAwsIntegration extends WebSocketTwoWayRouteIntegration { /** * @param id id of the underlying integration construct */ @@ -121,13 +121,4 @@ export class WebSocketAwsIntegration extends WebSocketRouteIntegration { timeout: this.props.timeout, }; } - - /** - * Add a response to this integration - * - * @param response The response to add - */ - addResponse(response: WebSocketIntegrationResponseProps) { - super.addResponse(response); - } } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts index d471fd70f4cb2..c13c9447e8111 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts @@ -6,6 +6,7 @@ import { ContentHandling, WebSocketIntegrationResponse, WebSocketIntegrationResponseProps, + WebSocketTwoWayRouteIntegration, } from '../../../aws-apigatewayv2'; import { ServicePrincipal } from '../../../aws-iam'; import { IFunction } from '../../../aws-lambda'; @@ -43,7 +44,7 @@ export interface WebSocketLambdaIntegrationProps { /** * Lambda WebSocket Integration */ -export class WebSocketLambdaIntegration extends WebSocketRouteIntegration { +export class WebSocketLambdaIntegration extends WebSocketTwoWayRouteIntegration { private readonly _id: string; @@ -88,13 +89,4 @@ export class WebSocketLambdaIntegration extends WebSocketRouteIntegration { responses: this.props.responses, }; } - - /** - * Add a response to this integration - * - * @param response The response to add - */ - addResponse(response: WebSocketIntegrationResponse) { - super.addResponse(response); - } } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts index 8425de6cd3718..eef289bfad077 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts @@ -1,5 +1,5 @@ import { Match, Template } from '../../../assertions'; -import { ContentHandling, WebSocketApi, WebSocketIntegrationResponseKey, WebSocketIntegrationResponseProps, WebSocketIntegrationType, WebSocketRouteIntegration, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig } from '../../../aws-apigatewayv2'; +import { ContentHandling, WebSocketApi, WebSocketIntegrationResponseKey, WebSocketIntegrationResponseProps, WebSocketIntegrationType, WebSocketRouteIntegration, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, WebSocketTwoWayRouteIntegration } from '../../../aws-apigatewayv2'; import * as iam from '../../../aws-iam'; import { Stack } from '../../../core'; @@ -18,7 +18,7 @@ interface WebSocketTestRouteIntegrationConfig { readonly responses?: WebSocketIntegrationResponseProps[]; } -class WebSocketTestIntegration extends WebSocketRouteIntegration { +class WebSocketTestIntegration extends WebSocketTwoWayRouteIntegration { constructor(id: string, private readonly props: WebSocketTestRouteIntegrationConfig) { super(id); } @@ -87,7 +87,7 @@ describe('WebSocketIntegrationRespons', () => { ], }); - api.addRoute('$default', { integration }); + api.addRoute('$default', { integration, returnResponse: true }); // THEN Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { @@ -129,7 +129,7 @@ describe('WebSocketIntegrationRespons', () => { ], }); - api.addRoute('$default', { integration }); + api.addRoute('$default', { integration, returnResponse: true }); // THEN Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { @@ -166,7 +166,7 @@ describe('WebSocketIntegrationRespons', () => { }); integration.addResponse({ responseKey: WebSocketIntegrationResponseKey.clientError }); - api.addRoute('$default', { integration }); + api.addRoute('$default', { integration, returnResponse: true }); // THEN Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { @@ -210,7 +210,7 @@ describe('WebSocketIntegrationRespons', () => { // THEN expect( - () => api.addRoute('$default', { integration }), + () => api.addRoute('$default', { integration, returnResponse: true }), ).toThrow(/Duplicate integration response key/); }); @@ -231,7 +231,27 @@ describe('WebSocketIntegrationRespons', () => { // THEN expect( - () => api.addRoute('$default', { integration }), + () => api.addRoute('$default', { integration, returnResponse: true }), ).toThrow(/Duplicate integration response key/); }); + + test('throws if returnResponse is not set to true', () => { + // GIVEN + const stack = new Stack(); + const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); + const api = new WebSocketApi(stack, 'Api'); + + // THEN + const integration = new WebSocketTestIntegration('TestIntegration', { + integrationUri: 'https://example.com', + responses: [ + { responseKey: WebSocketIntegrationResponseKey.default }, + ], + }); + + // THEN + expect( + () => api.addRoute('$default', { integration }), + ).toThrow(/Setting up integration responses without setting up returnResponse to true will have no effect/); + }); }); \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index 2a483771b8776..887cfeccb2016 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -77,7 +77,9 @@ export interface WebSocketIntegrationResponseProps { readonly responseKey: WebSocketIntegrationResponseKey; /** - * TODO + * The templates that are used to transform the integration response body. + * Specify templates as key-value pairs, with a content type as the key and + * a template as the value. * * @default - No response templates */ @@ -92,9 +94,20 @@ export interface WebSocketIntegrationResponseProps { readonly contentHandling?: ContentHandling; /** - * TODO + * The response parameters from the backend response that API Gateway sends + * to the method response. + * + * Use the destination as the key and the source as the value: + * + * - The destination must be an existing response parameter in the + * MethodResponse property. + * - The source must be an existing method request parameter or a static + * value. You must enclose static values in single quotation marks and + * pre-encode these values based on the destination specified in the + * request. * * @default - No response parameters + * @example { 'method.response.header.Content-Type': "'application/json'" } */ readonly responseParameters?: { [key: string]: string }; diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts index a060fe5fad290..948999aa2413f 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts @@ -204,14 +204,20 @@ export interface WebSocketRouteIntegrationBindOptions { * this will be used as their parent scope. */ readonly scope: Construct; + + /** + * Should the route send a response to the client + * @default false + */ + readonly returnResponse?: boolean; } /** - * The interface that various route integration classes will inherit. + * The abstract class that all route integration classes will implement. */ export abstract class WebSocketRouteIntegration { - private integration?: WebSocketIntegration; - private responses: WebSocketIntegrationResponseProps[] = []; + protected integration?: WebSocketIntegration; + protected config?: WebSocketRouteIntegrationConfig; /** * Initialize an integration for a route on websocket api. @@ -229,23 +235,66 @@ export abstract class WebSocketRouteIntegration { } if (!this.integration) { - const config = this.bind(options); + this.config = this.bind(options); this.integration = new WebSocketIntegration(options.scope, this.id, { webSocketApi: options.route.webSocketApi, - integrationType: config.type, - integrationUri: config.uri, - integrationMethod: config.method, - contentHandling: config.contentHandling, - credentialsRole: config.credentialsRole, - requestTemplates: config.requestTemplates, - requestParameters: config.requestParameters, - timeout: config.timeout, - passthroughBehavior: config.passthroughBehavior, - templateSelectionExpression: config.templateSelectionExpression, + integrationType: this.config.type, + integrationUri: this.config.uri, + integrationMethod: this.config.method, + contentHandling: this.config.contentHandling, + credentialsRole: this.config.credentialsRole, + requestTemplates: this.config.requestTemplates, + requestParameters: this.config.requestParameters, + timeout: this.config.timeout, + passthroughBehavior: this.config.passthroughBehavior, + templateSelectionExpression: this.config.templateSelectionExpression, }); + } + + return { integrationId: this.integration.integrationId }; + } + + /** + * Bind this integration to the route. + */ + public abstract bind(options: WebSocketRouteIntegrationBindOptions): WebSocketRouteIntegrationConfig; +} + +/** + * The abstract class that all two-way communication route integration classes will implement. + */ +export abstract class WebSocketTwoWayRouteIntegration extends WebSocketRouteIntegration { + private responses: WebSocketIntegrationResponseProps[] = []; + + /** + * Initialize an integration for a route on websocket api. + * @param id id of the underlying `WebSocketIntegration` construct. + */ + constructor(id: string) { + super(id); + } + + /** + * Internal method called when binding this integration to the route. + * @internal + */ + public _bindToRoute(options: WebSocketRouteIntegrationBindOptions): { readonly integrationId: string } { + const requiresBinding = !this.integration; + const result = super._bindToRoute(options); + + if (requiresBinding) { + // This should never happen, super._bindToRoute must have set up the integration + if (!this.config || !this.integration) { + throw new Error('Missing integration setup during WebSocketRouteIntegration._bindToRoute'); + } + + this.responses.push(...this.config.responses ?? []); + if (this.responses.length && !options.returnResponse) { + // FIXME change to a warning? + throw new Error('Setting up integration responses without setting up returnResponse to true will have no effect, and is likely a mistake.'); + } - this.responses.push(...config.responses ?? []); this.responses.reduce<{ [key: string]: string }>((acc, props) => { if (props.responseKey.key in acc) { throw new Error(`Duplicate integration response key: "${props.responseKey.key}"`); @@ -261,7 +310,7 @@ export abstract class WebSocketRouteIntegration { } } - return { integrationId: this.integration.integrationId }; + return result; } /** @@ -269,14 +318,9 @@ export abstract class WebSocketRouteIntegration { * * @param response The response to add */ - protected addResponse(response: WebSocketIntegrationResponseProps) { + addResponse(response: WebSocketIntegrationResponseProps) { this.responses.push(response); } - - /** - * Bind this integration to the route. - */ - public abstract bind(options: WebSocketRouteIntegrationBindOptions): WebSocketRouteIntegrationConfig; } /** diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts index 0114e13c50e0d..44574a0b30e39 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts @@ -94,6 +94,7 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute { const config = props.integration._bindToRoute({ route: this, scope: this, + returnResponse: props.returnResponse, }); const authorizer = props.authorizer ?? new WebSocketNoneAuthorizer(); // must be explicitly NONE (not undefined) for stack updates to work correctly From 45e5cd39ef598e17ad2ea5a2be523515b5e2b43b Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sun, 31 Mar 2024 14:51:14 +0200 Subject: [PATCH 08/39] chore: fix unit tests --- .../aws-apigatewayv2-integrations/test/websocket/aws.test.ts | 1 + .../aws-apigatewayv2-integrations/test/websocket/lambda.test.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/aws.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/aws.test.ts index 50d0013bc60b6..c1a4d79d8bea7 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/aws.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/aws.test.ts @@ -49,6 +49,7 @@ describe('AwsWebSocketIntegration', () => { timeout: Duration.seconds(10), responses: [{ responseKey: WebSocketIntegrationResponseKey.success }], }), + returnResponse: true, }, }); diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/lambda.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/lambda.test.ts index c21a2d629cd1f..f47d5e96bf825 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/lambda.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/lambda.test.ts @@ -66,6 +66,7 @@ describe('LambdaWebSocketIntegration', () => { responses: [{ responseKey: WebSocketIntegrationResponseKey.success }], }, ), + returnResponse: true, }, }); From 0b83d030caa399dac1dc643b049a784bf9df67c3 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sun, 31 Mar 2024 14:54:37 +0200 Subject: [PATCH 09/39] chore: remove unused imports --- .../aws-apigatewayv2-integrations/lib/websocket/lambda.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts index c13c9447e8111..7b5adad971ccd 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts @@ -1,10 +1,8 @@ import { - WebSocketRouteIntegration, WebSocketIntegrationType, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, ContentHandling, - WebSocketIntegrationResponse, WebSocketIntegrationResponseProps, WebSocketTwoWayRouteIntegration, } from '../../../aws-apigatewayv2'; From 9b3422913f13082996e9f0b4bcd3b300f5b9e0ad Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sun, 31 Mar 2024 15:59:31 +0200 Subject: [PATCH 10/39] chore: build fixes --- .../lib/websocket/aws.ts | 4 +- .../lib/websocket/lambda.ts | 4 +- .../websocket/integration-response.test.ts | 6 +- .../lib/websocket/integration-response.ts | 69 +++++++++++-------- .../lib/websocket/integration.ts | 23 +++++-- .../aws-apigatewayv2/lib/websocket/route.ts | 5 -- packages/aws-cdk-lib/awslint.json | 2 + 7 files changed, 66 insertions(+), 47 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts index 8d286f4ba0cfb..789647bb885ef 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts @@ -4,8 +4,8 @@ import { WebSocketRouteIntegrationBindOptions, PassthroughBehavior, ContentHandling, - WebSocketIntegrationResponseProps, WebSocketTwoWayRouteIntegration, + InternalWebSocketIntegrationResponseProps, } from '../../../aws-apigatewayv2'; import { IRole } from '../../../aws-iam'; import { Duration } from '../../../core'; @@ -66,7 +66,7 @@ export interface WebSocketAwsIntegrationProps { * @default - No response configuration provided. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html */ - readonly responses?: WebSocketIntegrationResponseProps[]; + readonly responses?: InternalWebSocketIntegrationResponseProps[]; /** * The template selection expression for the integration. diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts index 7b5adad971ccd..e907d3964d762 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts @@ -3,8 +3,8 @@ import { WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, ContentHandling, - WebSocketIntegrationResponseProps, WebSocketTwoWayRouteIntegration, + InternalWebSocketIntegrationResponseProps, } from '../../../aws-apigatewayv2'; import { ServicePrincipal } from '../../../aws-iam'; import { IFunction } from '../../../aws-lambda'; @@ -36,7 +36,7 @@ export interface WebSocketLambdaIntegrationProps { * @default - No response configuration provided. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html */ - readonly responses?: WebSocketIntegrationResponseProps[]; + readonly responses?: InternalWebSocketIntegrationResponseProps[]; } /** diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts index eef289bfad077..6f7fbc4cafb36 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts @@ -1,5 +1,5 @@ import { Match, Template } from '../../../assertions'; -import { ContentHandling, WebSocketApi, WebSocketIntegrationResponseKey, WebSocketIntegrationResponseProps, WebSocketIntegrationType, WebSocketRouteIntegration, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, WebSocketTwoWayRouteIntegration } from '../../../aws-apigatewayv2'; +import { ContentHandling, InternalWebSocketIntegrationResponseProps, WebSocketApi, WebSocketIntegrationResponseKey, WebSocketIntegrationType, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, WebSocketTwoWayRouteIntegration } from '../../../aws-apigatewayv2'; import * as iam from '../../../aws-iam'; import { Stack } from '../../../core'; @@ -15,7 +15,7 @@ interface WebSocketTestRouteIntegrationConfig { * @default - No response configuration provided. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html */ - readonly responses?: WebSocketIntegrationResponseProps[]; + readonly responses?: InternalWebSocketIntegrationResponseProps[]; } class WebSocketTestIntegration extends WebSocketTwoWayRouteIntegration { @@ -36,7 +36,7 @@ class WebSocketTestIntegration extends WebSocketTwoWayRouteIntegration { * * @param response The response to add */ - addResponse(response: WebSocketIntegrationResponseProps) { + addResponse(response: InternalWebSocketIntegrationResponseProps) { super.addResponse(response); } } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index 887cfeccb2016..2320b4a6bcaf3 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -1,6 +1,6 @@ import { Construct } from 'constructs'; import { ContentHandling, IWebSocketIntegration } from './integration'; -import { IResource, Names, Resource } from '../../../core'; +import { IResource, Resource } from '../../../core'; import { CfnIntegrationResponse } from '../apigatewayv2.generated'; /** @@ -62,15 +62,26 @@ export class WebSocketIntegrationResponseKey { return new WebSocketIntegrationResponseKey(`/${httpStatusRegExp.source}/`); } + /** + * WebSocket integration response private constructor + * + * @param key The key of the integration response + */ private constructor(readonly key: string) {} + + /** String representation of the integration response key */ + public toString(): string { + return this.key; + } } /** - * WebSocket integration response properties + * WebSocket integration response properties, used internally for Integration implementations + * The integration will add itself these props during the bind process * * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html */ -export interface WebSocketIntegrationResponseProps { +export interface InternalWebSocketIntegrationResponseProps { /** * The HTTP status code or regular expression the response will be mapped to */ @@ -121,61 +132,63 @@ export interface WebSocketIntegrationResponseProps { } /** - * Represents an Integration Response for an WebSocket API. + * WebSocket integration response properties + * + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html */ -export interface IWebSocketIntegrationResponse extends IResource { +export interface WebSocketIntegrationResponseProps extends InternalWebSocketIntegrationResponseProps { /** - * The integration the response will be mapped to + * The WebSocket Integration to associate the response with */ readonly integration: IWebSocketIntegration; +} + +/** + * Represents an Integration Response for an WebSocket API. + */ +export interface IWebSocketIntegrationResponse extends IResource { + /** The WebSocket Integration associated with this Response */ + readonly integration: IWebSocketIntegration; /** - * The integration response key. + * Id of the integration response. + * @attribute */ - readonly responseKey: WebSocketIntegrationResponseKey; + readonly integrationResponseId: string; } /** * WebSocket Integration Response resource class + * @resource AWS::ApiGatewayV2::IntegrationResponse */ export class WebSocketIntegrationResponse extends Resource implements IWebSocketIntegrationResponse { - /** - * The integration response key. - */ - readonly responseKey: WebSocketIntegrationResponseKey; + public readonly integrationResponseId: string; + public readonly integration: IWebSocketIntegration; /** * Generate an array of WebSocket Integration Response resources from a map * and associate them with a given WebSocket Integration * * @param scope The parent construct - * @param integration The WebSocket Integration to associate the responses with + * @param id The name of the integration response construct * @param props The configuration properties to create WebSocket Integration Responses from */ constructor( scope: Construct, - readonly integration: IWebSocketIntegration, + id: string, props: WebSocketIntegrationResponseProps, ) { - super( - scope, - Names.nodeUniqueId(integration.node) + slugify(props.responseKey.key) + 'IntegrationResponse', - ); - - new CfnIntegrationResponse(this, 'Resource', { - apiId: this.integration.webSocketApi.apiId, - integrationId: this.integration.integrationId, + super(scope, id); + const { ref } = new CfnIntegrationResponse(this, 'Resource', { + apiId: props.integration.webSocketApi.apiId, + integrationId: props.integration.integrationId, integrationResponseKey: props.responseKey.key, responseTemplates: props.responseTemplates, contentHandlingStrategy: props.contentHandling, responseParameters: props.responseParameters, templateSelectionExpression: props.templateSelectionExpression, }); - - this.responseKey = props.responseKey; + this.integrationResponseId = ref; + this.integration = props.integration; } } - -function slugify(x: string): string { - return x.replace(/[^a-zA-Z0-9]/g, ''); -} diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts index 948999aa2413f..0bd8b354b0214 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts @@ -1,10 +1,10 @@ import { Construct } from 'constructs'; import { IWebSocketApi } from './api'; -import { WebSocketIntegrationResponse, WebSocketIntegrationResponseProps } from './integration-response'; +import { InternalWebSocketIntegrationResponseProps, WebSocketIntegrationResponse } from './integration-response'; import { IWebSocketRoute } from './route'; import { CfnIntegration } from '.././index'; import { IRole } from '../../../aws-iam'; -import { Duration, Resource } from '../../../core'; +import { Duration, Names, Resource } from '../../../core'; import { IIntegration } from '../common'; /** @@ -265,7 +265,7 @@ export abstract class WebSocketRouteIntegration { * The abstract class that all two-way communication route integration classes will implement. */ export abstract class WebSocketTwoWayRouteIntegration extends WebSocketRouteIntegration { - private responses: WebSocketIntegrationResponseProps[] = []; + private responses: InternalWebSocketIntegrationResponseProps[] = []; /** * Initialize an integration for a route on websocket api. @@ -305,8 +305,13 @@ export abstract class WebSocketTwoWayRouteIntegration extends WebSocketRouteInte return acc; }, {}); - for (const response of this.responses) { - new WebSocketIntegrationResponse(options.scope, this.integration, response); + for (const responseProps of this.responses) { + new WebSocketIntegrationResponse( + options.scope, + // FIXME any better way to generate a unique id? + Names.nodeUniqueId(this.integration.node) + slugify(responseProps.responseKey.key) + 'IntegrationResponse', + { ...responseProps, integration: this.integration }, + ); } } @@ -318,7 +323,7 @@ export abstract class WebSocketTwoWayRouteIntegration extends WebSocketRouteInte * * @param response The response to add */ - addResponse(response: WebSocketIntegrationResponseProps) { + addResponse(response: InternalWebSocketIntegrationResponseProps) { this.responses.push(response); } } @@ -379,7 +384,7 @@ export interface WebSocketRouteIntegrationConfig { * @default - No response configuration provided. * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html */ - readonly responses?: WebSocketIntegrationResponseProps[]; + readonly responses?: InternalWebSocketIntegrationResponseProps[]; /** * Template selection expression @@ -403,3 +408,7 @@ export interface WebSocketRouteIntegrationConfig { */ readonly passthroughBehavior?: PassthroughBehavior; } + +function slugify(x: string): string { + return x.replace(/[^a-zA-Z0-9]/g, ''); +} \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts index 44574a0b30e39..996c85cdf533f 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts @@ -76,11 +76,6 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute { public readonly webSocketApi: IWebSocketApi; public readonly routeKey: string; - /** - * Integration response ID - */ - public readonly integrationResponseId?: string; - constructor(scope: Construct, id: string, props: WebSocketRouteProps) { super(scope, id); diff --git a/packages/aws-cdk-lib/awslint.json b/packages/aws-cdk-lib/awslint.json index 3e0a4fe6f0cb7..22d461a99296c 100644 --- a/packages/aws-cdk-lib/awslint.json +++ b/packages/aws-cdk-lib/awslint.json @@ -897,10 +897,12 @@ "props-physical-name:aws-cdk-lib.aws_apigatewayv2.HttpRouteProps", "props-physical-name:aws-cdk-lib.aws_apigatewayv2.WebSocketIntegrationProps", "props-physical-name:aws-cdk-lib.aws_apigatewayv2.WebSocketRouteProps", + "props-physical-name:aws-cdk-lib.aws_apigatewayv2.WebSocketIntegrationResponseProps", "from-method:aws-cdk-lib.aws_apigatewayv2.HttpIntegration", "from-method:aws-cdk-lib.aws_apigatewayv2.HttpRoute", "from-method:aws-cdk-lib.aws_apigatewayv2.WebSocketIntegration", "from-method:aws-cdk-lib.aws_apigatewayv2.WebSocketRoute", + "from-method:aws-cdk-lib.aws_apigatewayv2.WebSocketIntegrationResponse", "docs-public-apis:aws-cdk-lib.aws_appconfig.ConfigurationSourceType.S3", "docs-public-apis:aws-cdk-lib.aws_appconfig.ConfigurationSourceType.SSM_DOCUMENT", "docs-public-apis:aws-cdk-lib.aws_appconfig.ConfigurationSourceType.SSM_PARAMETER", From 8c5432d8ef35421f1187de4e557ac9acac838d8a Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Mon, 1 Apr 2024 16:32:41 +0200 Subject: [PATCH 11/39] fix: IntegrationResponse type compatibility --- .../lib/websocket/lambda.ts | 15 ++-------- .../lib/websocket/mock.ts | 18 ++++++++++-- .../test/websocket/lambda.test.ts | 6 ---- .../test/websocket/mock.test.ts | 28 ++++++++++++++++++- 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts index e907d3964d762..a249ae7e1fa19 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/lambda.ts @@ -3,8 +3,7 @@ import { WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, ContentHandling, - WebSocketTwoWayRouteIntegration, - InternalWebSocketIntegrationResponseProps, + WebSocketRouteIntegration, } from '../../../aws-apigatewayv2'; import { ServicePrincipal } from '../../../aws-iam'; import { IFunction } from '../../../aws-lambda'; @@ -29,21 +28,12 @@ export interface WebSocketLambdaIntegrationProps { * the route response or method response without modification. */ readonly contentHandling?: ContentHandling; - - /** - * Integration responses configuration - * - * @default - No response configuration provided. - * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html - */ - readonly responses?: InternalWebSocketIntegrationResponseProps[]; } /** * Lambda WebSocket Integration */ -export class WebSocketLambdaIntegration extends WebSocketTwoWayRouteIntegration { - +export class WebSocketLambdaIntegration extends WebSocketRouteIntegration { private readonly _id: string; /** @@ -84,7 +74,6 @@ export class WebSocketLambdaIntegration extends WebSocketTwoWayRouteIntegration uri: integrationUri, timeout: this.props.timeout, contentHandling: this.props.contentHandling, - responses: this.props.responses, }; } } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts index 3ebf7411930c7..29af31cfbb69a 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts @@ -1,19 +1,30 @@ import { - WebSocketRouteIntegration, WebSocketIntegrationType, WebSocketRouteIntegrationConfig, WebSocketRouteIntegrationBindOptions, + WebSocketTwoWayRouteIntegration, + InternalWebSocketIntegrationResponseProps, } from '../../../aws-apigatewayv2'; +export interface WebSocketMockIntegrationProps { + /** + * Integration responses configuration + * + * @default - No response configuration provided. + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html + */ + readonly responses?: InternalWebSocketIntegrationResponseProps[]; +} + /** * Mock WebSocket Integration */ -export class WebSocketMockIntegration extends WebSocketRouteIntegration { +export class WebSocketMockIntegration extends WebSocketTwoWayRouteIntegration { /** * @param id id of the underlying integration construct */ - constructor(id: string) { + constructor(id: string, private readonly props: WebSocketMockIntegrationProps = {}) { super(id); } @@ -22,6 +33,7 @@ export class WebSocketMockIntegration extends WebSocketRouteIntegration { return { type: WebSocketIntegrationType.MOCK, uri: '', + responses: this.props.responses, }; } } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/lambda.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/lambda.test.ts index f47d5e96bf825..ca8159e2165cb 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/lambda.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/lambda.test.ts @@ -63,7 +63,6 @@ describe('LambdaWebSocketIntegration', () => { { timeout: Duration.seconds(10), contentHandling: ContentHandling.CONVERT_TO_TEXT, - responses: [{ responseKey: WebSocketIntegrationResponseKey.success }], }, ), returnResponse: true, @@ -77,11 +76,6 @@ describe('LambdaWebSocketIntegration', () => { TimeoutInMillis: 10000, ContentHandlingStrategy: 'CONVERT_TO_TEXT', }); - Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { - ApiId: { Ref: 'ApiF70053CD' }, - IntegrationId: { Ref: 'ApiconnectRouteIntegration5AB58E39' }, - IntegrationResponseKey: '/2\\d{2}/', - }); }); }); diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts index 3f08838edfb32..70e0366efbb3a 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts @@ -1,6 +1,6 @@ import { WebSocketMockIntegration } from './../../lib/websocket/mock'; import { Template } from '../../../assertions'; -import { WebSocketApi } from '../../../aws-apigatewayv2'; +import { WebSocketApi, WebSocketIntegrationResponseKey } from '../../../aws-apigatewayv2'; import { Stack } from '../../../core'; describe('MockWebSocketIntegration', () => { @@ -19,4 +19,30 @@ describe('MockWebSocketIntegration', () => { IntegrationUri: '', }); }); + + test('can set custom properties', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new WebSocketApi(stack, 'Api', { + defaultRouteOptions: { + integration: new WebSocketMockIntegration('DefaultIntegration', { + responses: [{ responseKey: WebSocketIntegrationResponseKey.success }], + }), + returnResponse: true, + }, + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'MOCK', + IntegrationUri: '', + }); + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { + ApiId: { Ref: 'ApiF70053CD' }, + IntegrationId: { Ref: 'ApidefaultRouteDefaultIntegrationE3602C1B' }, + IntegrationResponseKey: '/2\\d{2}/', + }); + }); }); From b0bfe571d4ba3abf248e293baa36297a70df7f5e Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Mon, 1 Apr 2024 16:38:22 +0200 Subject: [PATCH 12/39] chore: add jsdoc --- .../aws-apigatewayv2-integrations/lib/websocket/mock.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts index 29af31cfbb69a..8538ad8f6d799 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts @@ -6,6 +6,9 @@ import { InternalWebSocketIntegrationResponseProps, } from '../../../aws-apigatewayv2'; +/** + * Props for Mock type integration for a WebSocket Api. + */ export interface WebSocketMockIntegrationProps { /** * Integration responses configuration From 38a0d738dda14947bfa5f8ff84f56131df1d5903 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Mon, 1 Apr 2024 18:40:33 +0200 Subject: [PATCH 13/39] chore: update AWS integ --- ...nteg-aws-websocket-integration.assets.json | 4 +- ...eg-aws-websocket-integration.template.json | 100 ++++++++++-- .../integ.aws.js.snapshot/manifest.json | 40 ++++- .../websocket/integ.aws.js.snapshot/tree.json | 144 ++++++++++++++++-- .../test/websocket/integ.aws.ts | 56 ++++--- 5 files changed, 290 insertions(+), 54 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json index 46b3a9c480519..683ca8166b909 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json @@ -1,7 +1,7 @@ { "version": "36.0.0", "files": { - "636fdd026b7f14567bc975abac73dfbac55058665c31d090375174adc715aab5": { + "9fa081d322ebd6590989ef309a45cd2247a2605ec463da5bb2e4794fa6233c68": { "source": { "path": "integ-aws-websocket-integration.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "636fdd026b7f14567bc975abac73dfbac55058665c31d090375174adc715aab5.json", + "objectKey": "9fa081d322ebd6590989ef309a45cd2247a2605ec463da5bb2e4794fa6233c68.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json index 683dde85dc2c3..ad91ba4a75048 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json @@ -78,6 +78,7 @@ }, "AuthorizationType": "NONE", "RouteKey": "$default", + "RouteResponseSelectionExpression": "$default", "Target": { "Fn::Join": [ "", @@ -91,13 +92,24 @@ } } }, - "mywsapiconnectRouteDynamodbPutItem9E189A39": { + "mywsapidefaultRouteResponse9B924040": { + "Type": "AWS::ApiGatewayV2::RouteResponse", + "Properties": { + "ApiId": { + "Ref": "mywsapi32E6CE11" + }, + "RouteId": { + "Ref": "mywsapidefaultRouteE9382DF8" + }, + "RouteResponseKey": "$default" + } + }, + "mywsapiputItemRouteDynamodbPutItem3BF52E5B": { "Type": "AWS::ApiGatewayV2::Integration", "Properties": { "ApiId": { "Ref": "mywsapi32E6CE11" }, - "ContentHandlingStrategy": "CONVERT_TO_BINARY", "CredentialsArn": { "Fn::GetAtt": [ "ApiGatewayRoleD2518903", @@ -118,20 +130,17 @@ ] ] }, - "PassthroughBehavior": "WHEN_NO_TEMPLATES", - "RequestParameters": { - "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'" - }, + "PassthroughBehavior": "NEVER", "RequestTemplates": { - "application/json": { + "$default": { "Fn::Join": [ "", [ - "{\"TableName\":\"", + "{\n \"TableName\": \"", { "Ref": "MyTable794EDED1" }, - "\",\"Item\":{\"id\":{\"S\":\"$context.requestId\"}}}" + "\",\n \"Item\": {\n \"id\": { \"S\": \"$context.requestId\" },\n \"userData\": { \"S\": $input.json('$.data') },\n },\n }" ] ] } @@ -140,27 +149,70 @@ "TimeoutInMillis": 10000 } }, - "mywsapiconnectRoute45A0ED6A": { + "mywsapiputItemRouteintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse2F8E9302": { + "Type": "AWS::ApiGatewayV2::IntegrationResponse", + "Properties": { + "ApiId": { + "Ref": "mywsapi32E6CE11" + }, + "IntegrationId": { + "Ref": "mywsapiputItemRouteDynamodbPutItem3BF52E5B" + }, + "IntegrationResponseKey": "$default", + "ResponseTemplates": { + "application/json": "{\"success\":true}" + } + } + }, + "mywsapiputItemRouteintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponseF586C3A5": { + "Type": "AWS::ApiGatewayV2::IntegrationResponse", + "Properties": { + "ApiId": { + "Ref": "mywsapi32E6CE11" + }, + "IntegrationId": { + "Ref": "mywsapiputItemRouteDynamodbPutItem3BF52E5B" + }, + "IntegrationResponseKey": "/4\\d{2}/", + "ResponseTemplates": { + "application/json": "{\"error\":\"Bad request\"}" + } + } + }, + "mywsapiputItemRoute3F16A651": { "Type": "AWS::ApiGatewayV2::Route", "Properties": { "ApiId": { "Ref": "mywsapi32E6CE11" }, "AuthorizationType": "NONE", - "RouteKey": "$connect", + "RouteKey": "putItem", + "RouteResponseSelectionExpression": "$default", "Target": { "Fn::Join": [ "", [ "integrations/", { - "Ref": "mywsapiconnectRouteDynamodbPutItem9E189A39" + "Ref": "mywsapiputItemRouteDynamodbPutItem3BF52E5B" } ] ] } } }, + "mywsapiputItemRouteResponse6A675D0F": { + "Type": "AWS::ApiGatewayV2::RouteResponse", + "Properties": { + "ApiId": { + "Ref": "mywsapi32E6CE11" + }, + "RouteId": { + "Ref": "mywsapiputItemRoute3F16A651" + }, + "RouteResponseKey": "$default" + } + }, "DevStage520A913F": { "Type": "AWS::ApiGatewayV2::Stage", "Properties": { @@ -172,6 +224,30 @@ } } }, + "Outputs": { + "ApiEndpoint": { + "Value": { + "Fn::Join": [ + "", + [ + "wss://", + { + "Ref": "mywsapi32E6CE11" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/dev" + ] + ] + } + } + }, "Parameters": { "BootstrapVersion": { "Type": "AWS::SSM::Parameter::Value", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json index a472b3cafbe28..ca3ea39f22406 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/636fdd026b7f14567bc975abac73dfbac55058665c31d090375174adc715aab5.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/9fa081d322ebd6590989ef309a45cd2247a2605ec463da5bb2e4794fa6233c68.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -72,16 +72,40 @@ "data": "mywsapidefaultRouteE9382DF8" } ], - "/integ-aws-websocket-integration/mywsapi/$connect-Route/DynamodbPutItem/Resource": [ + "/integ-aws-websocket-integration/mywsapi/$default-Route/Response": [ { "type": "aws:cdk:logicalId", - "data": "mywsapiconnectRouteDynamodbPutItem9E189A39" + "data": "mywsapidefaultRouteResponse9B924040" } ], - "/integ-aws-websocket-integration/mywsapi/$connect-Route/Resource": [ + "/integ-aws-websocket-integration/mywsapi/putItem-Route/DynamodbPutItem/Resource": [ { "type": "aws:cdk:logicalId", - "data": "mywsapiconnectRoute45A0ED6A" + "data": "mywsapiputItemRouteDynamodbPutItem3BF52E5B" + } + ], + "/integ-aws-websocket-integration/mywsapi/putItem-Route/integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "mywsapiputItemRouteintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse2F8E9302" + } + ], + "/integ-aws-websocket-integration/mywsapi/putItem-Route/integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponse/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "mywsapiputItemRouteintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponseF586C3A5" + } + ], + "/integ-aws-websocket-integration/mywsapi/putItem-Route/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "mywsapiputItemRoute3F16A651" + } + ], + "/integ-aws-websocket-integration/mywsapi/putItem-Route/Response": [ + { + "type": "aws:cdk:logicalId", + "data": "mywsapiputItemRouteResponse6A675D0F" } ], "/integ-aws-websocket-integration/DevStage/Resource": [ @@ -90,6 +114,12 @@ "data": "DevStage520A913F" } ], + "/integ-aws-websocket-integration/ApiEndpoint": [ + { + "type": "aws:cdk:logicalId", + "data": "ApiEndpoint" + } + ], "/integ-aws-websocket-integration/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json index 8daa67ba0998a..0a1e738d11ca8 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json @@ -173,6 +173,7 @@ }, "authorizationType": "NONE", "routeKey": "$default", + "routeResponseSelectionExpression": "$default", "target": { "Fn::Join": [ "", @@ -190,6 +191,26 @@ "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnRoute", "version": "0.0.0" } + }, + "Response": { + "id": "Response", + "path": "integ-aws-websocket-integration/mywsapi/$default-Route/Response", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::RouteResponse", + "aws:cdk:cloudformation:props": { + "apiId": { + "Ref": "mywsapi32E6CE11" + }, + "routeId": { + "Ref": "mywsapidefaultRouteE9382DF8" + }, + "routeResponseKey": "$default" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnRouteResponse", + "version": "0.0.0" + } } }, "constructInfo": { @@ -197,24 +218,23 @@ "version": "0.0.0" } }, - "$connect-Route": { - "id": "$connect-Route", - "path": "integ-aws-websocket-integration/mywsapi/$connect-Route", + "putItem-Route": { + "id": "putItem-Route", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route", "children": { "DynamodbPutItem": { "id": "DynamodbPutItem", - "path": "integ-aws-websocket-integration/mywsapi/$connect-Route/DynamodbPutItem", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/DynamodbPutItem", "children": { "Resource": { "id": "Resource", - "path": "integ-aws-websocket-integration/mywsapi/$connect-Route/DynamodbPutItem/Resource", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/DynamodbPutItem/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::Integration", "aws:cdk:cloudformation:props": { "apiId": { "Ref": "mywsapi32E6CE11" }, - "contentHandlingStrategy": "CONVERT_TO_BINARY", "credentialsArn": { "Fn::GetAtt": [ "ApiGatewayRoleD2518903", @@ -235,20 +255,17 @@ ] ] }, - "passthroughBehavior": "WHEN_NO_TEMPLATES", - "requestParameters": { - "integration.request.header.Content-Type": "'application/x-www-form-urlencoded'" - }, + "passthroughBehavior": "NEVER", "requestTemplates": { - "application/json": { + "$default": { "Fn::Join": [ "", [ - "{\"TableName\":\"", + "{\n \"TableName\": \"", { "Ref": "MyTable794EDED1" }, - "\",\"Item\":{\"id\":{\"S\":\"$context.requestId\"}}}" + "\",\n \"Item\": {\n \"id\": { \"S\": \"$context.requestId\" },\n \"userData\": { \"S\": $input.json('$.data') },\n },\n }" ] ] } @@ -268,9 +285,75 @@ "version": "0.0.0" } }, + "integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse": { + "id": "integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::IntegrationResponse", + "aws:cdk:cloudformation:props": { + "apiId": { + "Ref": "mywsapi32E6CE11" + }, + "integrationId": { + "Ref": "mywsapiputItemRouteDynamodbPutItem3BF52E5B" + }, + "integrationResponseKey": "$default", + "responseTemplates": { + "application/json": "{\"success\":true}" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnIntegrationResponse", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketIntegrationResponse", + "version": "0.0.0" + } + }, + "integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponse": { + "id": "integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponse", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponse", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponse/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::IntegrationResponse", + "aws:cdk:cloudformation:props": { + "apiId": { + "Ref": "mywsapi32E6CE11" + }, + "integrationId": { + "Ref": "mywsapiputItemRouteDynamodbPutItem3BF52E5B" + }, + "integrationResponseKey": "/4\\d{2}/", + "responseTemplates": { + "application/json": "{\"error\":\"Bad request\"}" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnIntegrationResponse", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketIntegrationResponse", + "version": "0.0.0" + } + }, "Resource": { "id": "Resource", - "path": "integ-aws-websocket-integration/mywsapi/$connect-Route/Resource", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::Route", "aws:cdk:cloudformation:props": { @@ -278,14 +361,15 @@ "Ref": "mywsapi32E6CE11" }, "authorizationType": "NONE", - "routeKey": "$connect", + "routeKey": "putItem", + "routeResponseSelectionExpression": "$default", "target": { "Fn::Join": [ "", [ "integrations/", { - "Ref": "mywsapiconnectRouteDynamodbPutItem9E189A39" + "Ref": "mywsapiputItemRouteDynamodbPutItem3BF52E5B" } ] ] @@ -296,6 +380,26 @@ "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnRoute", "version": "0.0.0" } + }, + "Response": { + "id": "Response", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/Response", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::RouteResponse", + "aws:cdk:cloudformation:props": { + "apiId": { + "Ref": "mywsapi32E6CE11" + }, + "routeId": { + "Ref": "mywsapiputItemRoute3F16A651" + }, + "routeResponseKey": "$default" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnRouteResponse", + "version": "0.0.0" + } } }, "constructInfo": { @@ -337,6 +441,14 @@ "version": "0.0.0" } }, + "ApiEndpoint": { + "id": "ApiEndpoint", + "path": "integ-aws-websocket-integration/ApiEndpoint", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnOutput", + "version": "0.0.0" + } + }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "integ-aws-websocket-integration/BootstrapVersion", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.ts index c31f9aa5c5133..6257b7cfbbc48 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.ts @@ -1,13 +1,16 @@ -import { ContentHandling, HttpMethod, PassthroughBehavior, WebSocketApi, WebSocketStage } from 'aws-cdk-lib/aws-apigatewayv2'; +import { HttpMethod, PassthroughBehavior, WebSocketApi, WebSocketIntegrationResponseKey, WebSocketStage } from 'aws-cdk-lib/aws-apigatewayv2'; import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import * as iam from 'aws-cdk-lib/aws-iam'; -import { App, Duration, RemovalPolicy, Stack } from 'aws-cdk-lib'; +import { App, CfnOutput, Duration, RemovalPolicy, Stack } from 'aws-cdk-lib'; import { WebSocketAwsIntegration, WebSocketMockIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations'; import { IntegTest } from '@aws-cdk/integ-tests-alpha'; /* * Stack verification steps: - * 1. Verify manually that the integration has type "MOCK" + * 1. Connect: 'wscat -c '. Should connect successfully + * 2. Sending: '> {"action":"putItem", "data": "valid"}' should return {sucess: true} + * and add an item to the table, with the userData field set to "valid" + * 2. Sending: '> {"action":"putItem", "data": 1}' should return {error: "Bad request"} and not insert an item to the table */ const app = new App(); @@ -27,41 +30,56 @@ const apiRole = new iam.Role(stack, 'ApiGatewayRole', { }); const webSocketApi = new WebSocketApi(stack, 'mywsapi', { - defaultRouteOptions: { integration: new WebSocketMockIntegration('DefaultIntegration') }, + defaultRouteOptions: { + integration: new WebSocketMockIntegration('DefaultIntegration'), + returnResponse: true, + }, }); // Optionally, create a WebSocket stage -new WebSocketStage(stack, 'DevStage', { +const stage = new WebSocketStage(stack, 'DevStage', { webSocketApi: webSocketApi, stageName: 'dev', autoDeploy: true, }); -webSocketApi.addRoute('$connect', { +webSocketApi.addRoute('putItem', { integration: new WebSocketAwsIntegration('DynamodbPutItem', { integrationUri: `arn:aws:apigateway:${stack.region}:dynamodb:action/PutItem`, integrationMethod: HttpMethod.POST, credentialsRole: apiRole, - requestParameters: { - 'integration.request.header.Content-Type': '\'application/x-www-form-urlencoded\'', - }, requestTemplates: { - 'application/json': JSON.stringify({ - TableName: table.tableName, - Item: { - id: { - S: '$context.requestId', - }, - }, - }), + $default: `{ + "TableName": "${table.tableName}", + "Item": { + "id": { "S": "$context.requestId" }, + "userData": { "S": $input.json('$.data') } + } + }`, }, + responses: [ + { + responseKey: WebSocketIntegrationResponseKey.default, + responseTemplates: { + 'application/json': JSON.stringify({ success: true }), + }, + }, + { + responseKey: WebSocketIntegrationResponseKey.clientError, + responseTemplates: { + 'application/json': JSON.stringify({ error: 'Bad request' }), + }, + }, + ], templateSelectionExpression: '\\$default', - passthroughBehavior: PassthroughBehavior.WHEN_NO_TEMPLATES, - contentHandling: ContentHandling.CONVERT_TO_BINARY, + passthroughBehavior: PassthroughBehavior.NEVER, timeout: Duration.seconds(10), }), + returnResponse: true, }); +new CfnOutput(stack, 'ApiEndpoint', { value: stage.url }); + new IntegTest(app, 'apigatewayv2-aws-integration-integ-test', { testCases: [stack], cdkCommandOptions: { From 1bf091d6738e242dd761285da0dea714fcd6212e Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Mon, 1 Apr 2024 18:42:55 +0200 Subject: [PATCH 14/39] chore: add breaking change --- allowed-breaking-changes.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index 8a8f19f3e48a5..0caf6e69e4644 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -239,3 +239,6 @@ removed:aws-cdk-lib.custom_resources.LogOptions removed:aws-cdk-lib.custom_resources.WaiterStateMachineProps removed:aws-cdk-lib.custom_resources.ProviderProps.disableWaiterStateMachineLogging removed:aws-cdk-lib.custom_resources.ProviderProps.waiterStateMachineLogOptions + +# This member was added but never implemented, and could not have suited a proper implementation +removed:aws-cdk-lib.aws_apigatewayv2.WebSocketRoute.integrationResponseId From 3a00e502c0e053cb81e72435bf219ea1b703ec96 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Mon, 1 Apr 2024 22:28:12 +0200 Subject: [PATCH 15/39] chore: update integ --- .../integ-aws-websocket-integration.assets.json | 4 ++-- .../integ-aws-websocket-integration.template.json | 2 +- .../test/websocket/integ.aws.js.snapshot/manifest.json | 2 +- .../test/websocket/integ.aws.js.snapshot/tree.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json index 683ca8166b909..321a67bc33df3 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json @@ -1,7 +1,7 @@ { "version": "36.0.0", "files": { - "9fa081d322ebd6590989ef309a45cd2247a2605ec463da5bb2e4794fa6233c68": { + "af961815613a0b82b08a2c9890c91a8b84c357304dee79cb9407cecbe52f867f": { "source": { "path": "integ-aws-websocket-integration.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "9fa081d322ebd6590989ef309a45cd2247a2605ec463da5bb2e4794fa6233c68.json", + "objectKey": "af961815613a0b82b08a2c9890c91a8b84c357304dee79cb9407cecbe52f867f.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json index ad91ba4a75048..9b860e89127ef 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json @@ -140,7 +140,7 @@ { "Ref": "MyTable794EDED1" }, - "\",\n \"Item\": {\n \"id\": { \"S\": \"$context.requestId\" },\n \"userData\": { \"S\": $input.json('$.data') },\n },\n }" + "\",\n \"Item\": {\n \"id\": { \"S\": \"$context.requestId\" },\n \"userData\": { \"S\": $input.json('$.data') }\n }\n }" ] ] } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json index ca3ea39f22406..207f2fc127612 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/9fa081d322ebd6590989ef309a45cd2247a2605ec463da5bb2e4794fa6233c68.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/af961815613a0b82b08a2c9890c91a8b84c357304dee79cb9407cecbe52f867f.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json index 0a1e738d11ca8..761716931a842 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json @@ -265,7 +265,7 @@ { "Ref": "MyTable794EDED1" }, - "\",\n \"Item\": {\n \"id\": { \"S\": \"$context.requestId\" },\n \"userData\": { \"S\": $input.json('$.data') },\n },\n }" + "\",\n \"Item\": {\n \"id\": { \"S\": \"$context.requestId\" },\n \"userData\": { \"S\": $input.json('$.data') }\n }\n }" ] ] } From 7590d9664685fd0780711305c600dda4b14a5b58 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Mon, 1 Apr 2024 22:48:26 +0200 Subject: [PATCH 16/39] feat: add missing mock properties --- .../lib/websocket/mock.ts | 21 +++++++++++++++++++ .../test/websocket/mock.test.ts | 4 ++++ 2 files changed, 25 insertions(+) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts index 8538ad8f6d799..fe5909e4d2da6 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts @@ -10,6 +10,25 @@ import { * Props for Mock type integration for a WebSocket Api. */ export interface WebSocketMockIntegrationProps { + /** + * A map of Apache Velocity templates that are applied on the request + * payload. + * + * ``` + * { "application/json": "{ \"statusCode\": 200 }" } + * ``` + * + * @default - No request template provided to the integration. + */ + readonly requestTemplates?: { [contentType: string]: string }; + + /** + * The template selection expression for the integration. + * + * @default - No template selection expression provided. + */ + readonly templateSelectionExpression?: string; + /** * Integration responses configuration * @@ -36,6 +55,8 @@ export class WebSocketMockIntegration extends WebSocketTwoWayRouteIntegration { return { type: WebSocketIntegrationType.MOCK, uri: '', + requestTemplates: this.props.requestTemplates, + templateSelectionExpression: this.props.templateSelectionExpression, responses: this.props.responses, }; } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts index 70e0366efbb3a..713e9f337e31a 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts @@ -28,6 +28,8 @@ describe('MockWebSocketIntegration', () => { new WebSocketApi(stack, 'Api', { defaultRouteOptions: { integration: new WebSocketMockIntegration('DefaultIntegration', { + requestTemplates: { 'application/json': '{ "statusCode": 200 }' }, + templateSelectionExpression: '\\$default', responses: [{ responseKey: WebSocketIntegrationResponseKey.success }], }), returnResponse: true, @@ -38,6 +40,8 @@ describe('MockWebSocketIntegration', () => { Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { IntegrationType: 'MOCK', IntegrationUri: '', + RequestTemplates: { 'application/json': '{ "statusCode": 200 }' }, + TemplateSelectionExpression: '\\$default', }); Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { ApiId: { Ref: 'ApiF70053CD' }, From 70ce981a611e0521aebd635a74fb34cbfc494b6b Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Mon, 1 Apr 2024 23:05:37 +0200 Subject: [PATCH 17/39] chore: update mock integ --- .../test/websocket/integ.aws.ts | 2 +- ...efaultTestDeployAssert3D8FA778.assets.json | 19 ++ ...aultTestDeployAssert3D8FA778.template.json | 36 ++++ .../websocket/integ.mock.js.snapshot/cdk.out | 2 +- ...teg-mock-websocket-integration.assets.json | 6 +- ...g-mock-websocket-integration.template.json | 45 +++- .../integ.mock.js.snapshot/integ.json | 12 +- .../integ.mock.js.snapshot/manifest.json | 81 ++++++- .../integ.mock.js.snapshot/tree.json | 197 ++++++++++++++---- .../test/websocket/integ.mock.ts | 25 ++- 10 files changed, 358 insertions(+), 67 deletions(-) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.template.json diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.ts index 6257b7cfbbc48..4e09b0214fdb8 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.ts @@ -8,7 +8,7 @@ import { IntegTest } from '@aws-cdk/integ-tests-alpha'; /* * Stack verification steps: * 1. Connect: 'wscat -c '. Should connect successfully - * 2. Sending: '> {"action":"putItem", "data": "valid"}' should return {sucess: true} + * 2. Sending: '> {"action":"putItem", "data": "valid"}' should return {success: true} * and add an item to the table, with the userData field set to "valid" * 2. Sending: '> {"action":"putItem", "data": 1}' should return {error: "Bad request"} and not insert an item to the table */ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets.json new file mode 100644 index 0000000000000..9963729af1964 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets.json @@ -0,0 +1,19 @@ +{ + "version": "36.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/cdk.out index 8ecc185e9dbee..1f0068d32659a 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"21.0.0"} \ No newline at end of file +{"version":"36.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json index 89f3532813157..13299fa1bc91a 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json @@ -1,7 +1,7 @@ { - "version": "20.0.0", + "version": "36.0.0", "files": { - "aabdbe840e6768f96ec51dd87886969be769aeca5a21773e27cd16f1a90367fe": { + "7cf95007ced5671e325821ac7178a5c0ede1f7caaa2ac8e08bff1eb3b4ea7883": { "source": { "path": "integ-mock-websocket-integration.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "aabdbe840e6768f96ec51dd87886969be769aeca5a21773e27cd16f1a90367fe.json", + "objectKey": "7cf95007ced5671e325821ac7178a5c0ede1f7caaa2ac8e08bff1eb3b4ea7883.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json index 085584d49a05b..b52fd9d280727 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json @@ -24,8 +24,8 @@ "ApiId": { "Ref": "mywsapi32E6CE11" }, - "RouteKey": "$default", "AuthorizationType": "NONE", + "RouteKey": "$default", "Target": { "Fn::Join": [ "", @@ -39,14 +39,32 @@ } } }, - "mywsapisendmessageRouteSendMessageIntegrationD29E12F9": { + "mywsapisendmessageRouteDefaultIntegration702159AD": { "Type": "AWS::ApiGatewayV2::Integration", "Properties": { "ApiId": { "Ref": "mywsapi32E6CE11" }, "IntegrationType": "MOCK", - "IntegrationUri": "" + "IntegrationUri": "", + "RequestTemplates": { + "application/json": "{\"statusCode\":200}" + } + } + }, + "mywsapisendmessageRouteintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse72380ED8": { + "Type": "AWS::ApiGatewayV2::IntegrationResponse", + "Properties": { + "ApiId": { + "Ref": "mywsapi32E6CE11" + }, + "IntegrationId": { + "Ref": "mywsapisendmessageRouteDefaultIntegration702159AD" + }, + "IntegrationResponseKey": "$default", + "ResponseTemplates": { + "$default": "{\"success\":true}" + } } }, "mywsapisendmessageRouteAE873328": { @@ -55,29 +73,42 @@ "ApiId": { "Ref": "mywsapi32E6CE11" }, - "RouteKey": "sendmessage", "AuthorizationType": "NONE", + "RouteKey": "sendmessage", + "RouteResponseSelectionExpression": "$default", "Target": { "Fn::Join": [ "", [ "integrations/", { - "Ref": "mywsapisendmessageRouteSendMessageIntegrationD29E12F9" + "Ref": "mywsapisendmessageRouteDefaultIntegration702159AD" } ] ] } } }, + "mywsapisendmessageRouteResponse2ED167D2": { + "Type": "AWS::ApiGatewayV2::RouteResponse", + "Properties": { + "ApiId": { + "Ref": "mywsapi32E6CE11" + }, + "RouteId": { + "Ref": "mywsapisendmessageRouteAE873328" + }, + "RouteResponseKey": "$default" + } + }, "mystage114C35EC": { "Type": "AWS::ApiGatewayV2::Stage", "Properties": { "ApiId": { "Ref": "mywsapi32E6CE11" }, - "StageName": "dev", - "AutoDeploy": true + "AutoDeploy": true, + "StageName": "dev" } } }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ.json index e38a89b873c9c..925cbd9e68810 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ.json @@ -1,14 +1,12 @@ { - "version": "20.0.0", + "version": "36.0.0", "testCases": { - "integ.mock": { + "apigatewayv2-mock-integration-integ-test/DefaultTest": { "stacks": [ "integ-mock-websocket-integration" ], - "diffAssets": false, - "stackUpdateWorkflow": true + "assertionStack": "apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert", + "assertionStackName": "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778" } - }, - "synthContext": {}, - "enableLookups": false + } } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json index cf17e2f2db70c..c3c9699e24543 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json @@ -1,12 +1,6 @@ { - "version": "20.0.0", + "version": "36.0.0", "artifacts": { - "Tree": { - "type": "cdk:tree", - "properties": { - "file": "tree.json" - } - }, "integ-mock-websocket-integration.assets": { "type": "cdk:asset-manifest", "properties": { @@ -20,10 +14,11 @@ "environment": "aws://unknown-account/unknown-region", "properties": { "templateFile": "integ-mock-websocket-integration.template.json", + "terminationProtection": false, "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/aabdbe840e6768f96ec51dd87886969be769aeca5a21773e27cd16f1a90367fe.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/7cf95007ced5671e325821ac7178a5c0ede1f7caaa2ac8e08bff1eb3b4ea7883.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -57,10 +52,16 @@ "data": "mywsapidefaultRouteE9382DF8" } ], - "/integ-mock-websocket-integration/mywsapi/sendmessage-Route/SendMessageIntegration/Resource": [ + "/integ-mock-websocket-integration/mywsapi/sendmessage-Route/DefaultIntegration/Resource": [ { "type": "aws:cdk:logicalId", - "data": "mywsapisendmessageRouteSendMessageIntegrationD29E12F9" + "data": "mywsapisendmessageRouteDefaultIntegration702159AD" + } + ], + "/integ-mock-websocket-integration/mywsapi/sendmessage-Route/integmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "mywsapisendmessageRouteintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse72380ED8" } ], "/integ-mock-websocket-integration/mywsapi/sendmessage-Route/Resource": [ @@ -69,6 +70,12 @@ "data": "mywsapisendmessageRouteAE873328" } ], + "/integ-mock-websocket-integration/mywsapi/sendmessage-Route/Response": [ + { + "type": "aws:cdk:logicalId", + "data": "mywsapisendmessageRouteResponse2ED167D2" + } + ], "/integ-mock-websocket-integration/mystage/Resource": [ { "type": "aws:cdk:logicalId", @@ -95,6 +102,60 @@ ] }, "displayName": "integ-mock-websocket-integration" + }, + "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "apigatewayv2mockintegrationintegtestDefaultTestDeployAssert3D8FA778.assets" + ], + "metadata": { + "/apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } } } } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json index 62c47b25cc7d3..b86683bc63893 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json @@ -4,14 +4,6 @@ "id": "App", "path": "", "children": { - "Tree": { - "id": "Tree", - "path": "Tree", - "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" - } - }, "integ-mock-websocket-integration": { "id": "integ-mock-websocket-integration", "path": "integ-mock-websocket-integration", @@ -32,7 +24,7 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.CfnApi", + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnApi", "version": "0.0.0" } }, @@ -58,13 +50,13 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.CfnIntegration", + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnIntegration", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketIntegration", + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketIntegration", "version": "0.0.0" } }, @@ -77,8 +69,8 @@ "apiId": { "Ref": "mywsapi32E6CE11" }, - "routeKey": "$default", "authorizationType": "NONE", + "routeKey": "$default", "target": { "Fn::Join": [ "", @@ -93,13 +85,13 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.CfnRoute", + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnRoute", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketRoute", + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketRoute", "version": "0.0.0" } }, @@ -107,13 +99,13 @@ "id": "sendmessage-Route", "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route", "children": { - "SendMessageIntegration": { - "id": "SendMessageIntegration", - "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/SendMessageIntegration", + "DefaultIntegration": { + "id": "DefaultIntegration", + "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/DefaultIntegration", "children": { "Resource": { "id": "Resource", - "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/SendMessageIntegration/Resource", + "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/DefaultIntegration/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::Integration", "aws:cdk:cloudformation:props": { @@ -121,17 +113,53 @@ "Ref": "mywsapi32E6CE11" }, "integrationType": "MOCK", - "integrationUri": "" + "integrationUri": "", + "requestTemplates": { + "application/json": "{\"statusCode\":200}" + } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.CfnIntegration", + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnIntegration", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketIntegration", + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketIntegration", + "version": "0.0.0" + } + }, + "integmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse": { + "id": "integmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse", + "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/integmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/integmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::IntegrationResponse", + "aws:cdk:cloudformation:props": { + "apiId": { + "Ref": "mywsapi32E6CE11" + }, + "integrationId": { + "Ref": "mywsapisendmessageRouteDefaultIntegration702159AD" + }, + "integrationResponseKey": "$default", + "responseTemplates": { + "$default": "{\"success\":true}" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnIntegrationResponse", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketIntegrationResponse", "version": "0.0.0" } }, @@ -144,15 +172,16 @@ "apiId": { "Ref": "mywsapi32E6CE11" }, - "routeKey": "sendmessage", "authorizationType": "NONE", + "routeKey": "sendmessage", + "routeResponseSelectionExpression": "$default", "target": { "Fn::Join": [ "", [ "integrations/", { - "Ref": "mywsapisendmessageRouteSendMessageIntegrationD29E12F9" + "Ref": "mywsapisendmessageRouteDefaultIntegration702159AD" } ] ] @@ -160,19 +189,39 @@ } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.CfnRoute", + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnRoute", + "version": "0.0.0" + } + }, + "Response": { + "id": "Response", + "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/Response", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::RouteResponse", + "aws:cdk:cloudformation:props": { + "apiId": { + "Ref": "mywsapi32E6CE11" + }, + "routeId": { + "Ref": "mywsapisendmessageRouteAE873328" + }, + "routeResponseKey": "$default" + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnRouteResponse", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketRoute", + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketRoute", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketApi", + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketApi", "version": "0.0.0" } }, @@ -189,18 +238,18 @@ "apiId": { "Ref": "mywsapi32E6CE11" }, - "stageName": "dev", - "autoDeploy": true + "autoDeploy": true, + "stageName": "dev" } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.CfnStage", + "fqn": "aws-cdk-lib.aws_apigatewayv2.CfnStage", "version": "0.0.0" } } }, "constructInfo": { - "fqn": "@aws-cdk/aws-apigatewayv2.WebSocketStage", + "fqn": "aws-cdk-lib.aws_apigatewayv2.WebSocketStage", "version": "0.0.0" } }, @@ -208,20 +257,98 @@ "id": "ApiEndpoint", "path": "integ-mock-websocket-integration/ApiEndpoint", "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "aws-cdk-lib.CfnOutput", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-mock-websocket-integration/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-mock-websocket-integration/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + }, + "apigatewayv2-mock-integration-integ-test": { + "id": "apigatewayv2-mock-integration-integ-test", + "path": "apigatewayv2-mock-integration-integ-test", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "apigatewayv2-mock-integration-integ-test/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "apigatewayv2-mock-integration-integ-test/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "apigatewayv2-mock-integration-integ-test/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "aws-cdk-lib.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" } } }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", "constructInfo": { "fqn": "constructs.Construct", - "version": "10.1.85" + "version": "10.3.0" } } }, "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.85" + "fqn": "aws-cdk-lib.App", + "version": "0.0.0" } } } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.ts index 5ae9e8d7556be..f3ef371d4a967 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.ts @@ -1,10 +1,12 @@ -import { WebSocketApi, WebSocketStage } from 'aws-cdk-lib/aws-apigatewayv2'; +import { WebSocketApi, WebSocketIntegrationResponseKey, WebSocketStage } from 'aws-cdk-lib/aws-apigatewayv2'; import { App, CfnOutput, Stack } from 'aws-cdk-lib'; import { WebSocketMockIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; /* * Stack verification steps: - * 1. Verify manually that the integration has type "MOCK" + * 1. Connect: 'wscat -c '. Should connect successfully + * 2. Sending: '> {"action": "sendmessage"}' should return {success: true} */ const app = new App(); @@ -19,6 +21,23 @@ const stage = new WebSocketStage(stack, 'mystage', { autoDeploy: true, }); -webSocketApi.addRoute('sendmessage', { integration: new WebSocketMockIntegration('SendMessageIntegration') }); +webSocketApi.addRoute('sendmessage', { + integration: new WebSocketMockIntegration('DefaultIntegration', { + requestTemplates: { 'application/json': JSON.stringify({ statusCode: 200 }) }, + responses: [ + { + responseKey: WebSocketIntegrationResponseKey.default, + responseTemplates: { + $default: JSON.stringify({ success: true }), + }, + }, + ], + }), + returnResponse: true, +}); new CfnOutput(stack, 'ApiEndpoint', { value: stage.url }); + +new IntegTest(app, 'apigatewayv2-mock-integration-integ-test', { + testCases: [stack], +}); \ No newline at end of file From fa2e68e99f7585c0296f4a472ff9b6a000e16323 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Tue, 2 Apr 2024 07:53:21 +0200 Subject: [PATCH 18/39] chore: README update --- .../aws-apigatewayv2-integrations/README.md | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md index 8ab4cfc13903e..2e60536113fe7 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md @@ -312,6 +312,11 @@ See [Working with binary media types for WebSocket APIs](https://docs.aws.amazon ### Integration Responses +You can set up your integrations to send responses to your WebSocket client, using the [`returnResponse`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigatewayv2.WebSocketRouteOptions.html#returnresponse) field. + +Additionally, some integrations allow you to manipulate and customize your responses, mapped by HTTP response code. This can be done via the `responses` field, or the `addResponse` method. +See [Setting up a WebSocket API integration responses in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html). + ```ts import * as integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations'; @@ -322,31 +327,29 @@ new apigwv2.WebSocketStage(this, 'mystage', { autoDeploy: true, }); -declare const messageHandler: lambda.Function; -const integration = new integrations.WebSocketLambdaIntegration( - 'SendMessageIntegration', - messageHandler, - { - responses: [ - // Default response key, will be used if no other matched - { responseKey: integrations.WebSocketIntegrationResponseKey.default }, - // Success response key, will match all 2xx response HTTP status codes - { responseKey: integrations.WebSocketIntegrationResponseKey.success }, - // You can also create custom response integrations for specific status codes - { - responseKey: integrations.WebSocketIntegrationResponseKey.fromStatusCode(404), - contentHandling: ContentHandling.CONVERT_TO_BINARY, - responseParameters: { - 'method.response.header.Accept': "'application/json'", - }, - templateSelectionExpression: '$default', - responseTemplates: { - 'application/json': '{ "message": $context.error.message, "statusCode": 404 }', - }, - }, - ], +declare const integration: WebSocketAwsIntegration; + +// Default response key, will be used if no other matched +integration.addResponse({ responseKey: apigwv2.WebSocketIntegrationResponseKey.default }); + +integration.addResponse({ + // Success response key, will match all 2xx response HTTP status codes + responseKey: apigwv2.WebSocketIntegrationResponseKey.success, + responseTemplates: { + 'application/json': JSON.stringify({ success: true }), }, -); +}); + +integration.addResponse({ + // You can also create custom response integrations for specific status codes + responseKey: apigwv2.WebSocketIntegrationResponseKey.fromStatusCode(404), + responseTemplates: { + 'application/json': JSON.stringify({ + error: 'Not found', + requestId: '$context.requestId', + }), + }, +}); -webSocketApi.addRoute('sendMessage', { integration }); +webSocketApi.addRoute('putItem', { integration, returnResponse: true }); ``` \ No newline at end of file From 6655588c9480996bdc45d24e0cfc797abb2d741d Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Tue, 2 Apr 2024 08:19:00 +0200 Subject: [PATCH 19/39] fix: WebSocketIntegrationResponse constructor --- .../websocket/integration-response.test.ts | 78 +++++++++++++++---- .../lib/websocket/integration-response.ts | 10 +-- .../lib/websocket/integration.ts | 25 +++++- 3 files changed, 94 insertions(+), 19 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts index 6f7fbc4cafb36..ed1e7cd640432 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts @@ -1,5 +1,5 @@ import { Match, Template } from '../../../assertions'; -import { ContentHandling, InternalWebSocketIntegrationResponseProps, WebSocketApi, WebSocketIntegrationResponseKey, WebSocketIntegrationType, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, WebSocketTwoWayRouteIntegration } from '../../../aws-apigatewayv2'; +import { ContentHandling, InternalWebSocketIntegrationResponseProps, WebSocketApi, WebSocketIntegration, WebSocketIntegrationResponse, WebSocketIntegrationResponseKey, WebSocketIntegrationType, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, WebSocketTwoWayRouteIntegration } from '../../../aws-apigatewayv2'; import * as iam from '../../../aws-iam'; import { Stack } from '../../../core'; @@ -72,14 +72,70 @@ describe('WebSocketIntegrationResponseKey', () => { }); }); -describe('WebSocketIntegrationRespons', () => { - test('can set an integration response', () => { +describe('WebSocketIntegrationResponse from constructor', () => { + test('can create an integration response', () => { // GIVEN const stack = new Stack(); - const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); const api = new WebSocketApi(stack, 'Api'); + const integration = new WebSocketTestIntegration('TestIntegration', { + integrationUri: 'https://example.com', + responses: [ + { responseKey: WebSocketIntegrationResponseKey.default }, + ], + }); + api.addRoute('$default', { integration, returnResponse: true }); + + // WHEN + new WebSocketIntegrationResponse(stack, 'IntegrationResponse', { + integration, + responseKey: WebSocketIntegrationResponseKey.default, + }); // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'TEST', + IntegrationUri: 'https://example.com', + }); + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { + ApiId: { Ref: 'ApiF70053CD' }, + IntegrationId: { Ref: 'ApidefaultRouteTestIntegration7AF569F1' }, + IntegrationResponseKey: '$default', + TemplateSelectionExpression: Match.absent(), + ContentHandling: Match.absent(), + ResponseParameters: Match.absent(), + ResponseTemplates: Match.absent(), + }); + }); + + test('throws if addRoute has not been ran', () => { + + // GIVEN + const stack = new Stack(); + const api = new WebSocketApi(stack, 'Api'); + const integration = new WebSocketTestIntegration('TestIntegration', { + integrationUri: 'https://example.com', + responses: [ + { responseKey: WebSocketIntegrationResponseKey.default }, + ], + }); + + // THEN + expect(() => + new WebSocketIntegrationResponse(stack, 'IntegrationResponse', { + integration, + responseKey: WebSocketIntegrationResponseKey.default, + }), + ).toThrow(/This integration has not been associated to an API route/); + }); +}); + +describe('WebSocketIntegrationResponse from properties', () => { + test('can set an integration response', () => { + // GIVEN + const stack = new Stack(); + const api = new WebSocketApi(stack, 'Api'); + + // WHEN const integration = new WebSocketTestIntegration('TestIntegration', { integrationUri: 'https://example.com', responses: [ @@ -108,10 +164,9 @@ describe('WebSocketIntegrationRespons', () => { test('can set custom integration response properties', () => { // GIVEN const stack = new Stack(); - const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); const api = new WebSocketApi(stack, 'Api'); - // THEN + // WHEN const integration = new WebSocketTestIntegration('TestIntegration', { integrationUri: 'https://example.com', responses: [ @@ -154,10 +209,9 @@ describe('WebSocketIntegrationRespons', () => { test('can add integration responses', () => { // GIVEN const stack = new Stack(); - const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); const api = new WebSocketApi(stack, 'Api'); - // THEN + // WHEN const integration = new WebSocketTestIntegration('TestIntegration', { integrationUri: 'https://example.com', responses: [ @@ -196,10 +250,9 @@ describe('WebSocketIntegrationRespons', () => { test('throws if duplicate response key is set', () => { // GIVEN const stack = new Stack(); - const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); const api = new WebSocketApi(stack, 'Api'); - // THEN + // WHEN const integration = new WebSocketTestIntegration('TestIntegration', { integrationUri: 'https://example.com', responses: [ @@ -220,7 +273,7 @@ describe('WebSocketIntegrationRespons', () => { const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); const api = new WebSocketApi(stack, 'Api'); - // THEN + // WHEN const integration = new WebSocketTestIntegration('TestIntegration', { integrationUri: 'https://example.com', responses: [ @@ -238,10 +291,9 @@ describe('WebSocketIntegrationRespons', () => { test('throws if returnResponse is not set to true', () => { // GIVEN const stack = new Stack(); - const role = new iam.Role(stack, 'MyRole', { assumedBy: new iam.ServicePrincipal('foo') }); const api = new WebSocketApi(stack, 'Api'); - // THEN + // WHEN const integration = new WebSocketTestIntegration('TestIntegration', { integrationUri: 'https://example.com', responses: [ diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index 2320b4a6bcaf3..ad54d62e570ac 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -1,5 +1,5 @@ import { Construct } from 'constructs'; -import { ContentHandling, IWebSocketIntegration } from './integration'; +import { ContentHandling, WebSocketRouteIntegration } from './integration'; import { IResource, Resource } from '../../../core'; import { CfnIntegrationResponse } from '../apigatewayv2.generated'; @@ -140,7 +140,7 @@ export interface WebSocketIntegrationResponseProps extends InternalWebSocketInte /** * The WebSocket Integration to associate the response with */ - readonly integration: IWebSocketIntegration; + readonly integration: WebSocketRouteIntegration; } /** @@ -148,7 +148,7 @@ export interface WebSocketIntegrationResponseProps extends InternalWebSocketInte */ export interface IWebSocketIntegrationResponse extends IResource { /** The WebSocket Integration associated with this Response */ - readonly integration: IWebSocketIntegration; + readonly integration: WebSocketRouteIntegration; /** * Id of the integration response. @@ -163,7 +163,7 @@ export interface IWebSocketIntegrationResponse extends IResource { */ export class WebSocketIntegrationResponse extends Resource implements IWebSocketIntegrationResponse { public readonly integrationResponseId: string; - public readonly integration: IWebSocketIntegration; + public readonly integration: WebSocketRouteIntegration; /** * Generate an array of WebSocket Integration Response resources from a map @@ -180,7 +180,7 @@ export class WebSocketIntegrationResponse extends Resource implements IWebSocket ) { super(scope, id); const { ref } = new CfnIntegrationResponse(this, 'Resource', { - apiId: props.integration.webSocketApi.apiId, + apiId: props.integration.webSocketApiId, integrationId: props.integration.integrationId, integrationResponseKey: props.responseKey.key, responseTemplates: props.responseTemplates, diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts index 0bd8b354b0214..0b22350333489 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts @@ -259,6 +259,29 @@ export abstract class WebSocketRouteIntegration { * Bind this integration to the route. */ public abstract bind(options: WebSocketRouteIntegrationBindOptions): WebSocketRouteIntegrationConfig; + + /** + * The WebSocket API identifier + * @throws an error if this integration has not been bound to a route first + */ + public get webSocketApiId(): string { + if (!this.integration) { + throw new Error('This integration has not been associated to an API route'); + } + return this.integration.webSocketApi.apiId; + } + + /** + * The WebSocket Integration identifier + * @throws an error if this integration has not been bound to a route first + */ + public get integrationId(): string { + if (!this.integration) { + throw new Error('This integration has not been associated to an API route'); + } + + return this.integration.integrationId; + } } /** @@ -310,7 +333,7 @@ export abstract class WebSocketTwoWayRouteIntegration extends WebSocketRouteInte options.scope, // FIXME any better way to generate a unique id? Names.nodeUniqueId(this.integration.node) + slugify(responseProps.responseKey.key) + 'IntegrationResponse', - { ...responseProps, integration: this.integration }, + { ...responseProps, integration: this }, ); } } From 5bdc2427ee259500a0014bc961ded52dece1b1c5 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Tue, 2 Apr 2024 09:14:46 +0200 Subject: [PATCH 20/39] chore: docs fixes --- .../aws-cdk-lib/aws-apigatewayv2-integrations/README.md | 2 +- .../aws-apigatewayv2/lib/websocket/integration-response.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md index 2e60536113fe7..6d48e32e62863 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md @@ -318,7 +318,7 @@ Additionally, some integrations allow you to manipulate and customize your respo See [Setting up a WebSocket API integration responses in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html). ```ts -import * as integrations from 'aws-cdk-lib/aws-apigatewayv2-integrations'; +import { WebSocketAwsIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations'; const webSocketApi = new apigwv2.WebSocketApi(this, 'mywsapi'); new apigwv2.WebSocketStage(this, 'mystage', { diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index ad54d62e570ac..1a9782e62d777 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -32,7 +32,7 @@ export class WebSocketIntegrationResponseKey { * * @example * // Match 403 status code - * WebSocketIntegrationResponseKey.fromStatusCode(403) + * apigwv2.WebSocketIntegrationResponseKey.fromStatusCode(403) * * @param httpStatusCode HTTP status code of the mapped response */ @@ -46,10 +46,10 @@ export class WebSocketIntegrationResponseKey { * * @example * // Match all 20x status codes - * WebSocketIntegrationResponseKey.fromStatusRegExp('20\\d') + * apigwv2.WebSocketIntegrationResponseKey.fromStatusRegExp('20\\d') * * // Match all 4xx status codes, using RegExp - * WebSocketIntegrationResponseKey.fromStatusRegExp(/4\d{2}/.source) + * apigwv2.WebSocketIntegrationResponseKey.fromStatusRegExp(/4\d{2}/.source) * * @param httpStatusRegExpStr HTTP status code regular expression string representation * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp @@ -118,7 +118,6 @@ export interface InternalWebSocketIntegrationResponseProps { * request. * * @default - No response parameters - * @example { 'method.response.header.Content-Type': "'application/json'" } */ readonly responseParameters?: { [key: string]: string }; From 0287dc27fa9fc50578130bcb2962dbabff17000c Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Tue, 2 Apr 2024 09:58:32 +0200 Subject: [PATCH 21/39] chore: refactor CustomResponseWebSocketRoute --- .../aws-apigatewayv2-integrations/lib/websocket/aws.ts | 4 ++-- .../aws-apigatewayv2-integrations/lib/websocket/mock.ts | 4 ++-- .../test/websocket/integration-response.test.ts | 4 ++-- .../aws-apigatewayv2/lib/websocket/integration.ts | 5 +++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts index 789647bb885ef..dc1146faecd80 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts @@ -4,7 +4,7 @@ import { WebSocketRouteIntegrationBindOptions, PassthroughBehavior, ContentHandling, - WebSocketTwoWayRouteIntegration, + CustomResponseWebSocketRoute, InternalWebSocketIntegrationResponseProps, } from '../../../aws-apigatewayv2'; import { IRole } from '../../../aws-iam'; @@ -98,7 +98,7 @@ export interface WebSocketAwsIntegrationProps { /** * AWS WebSocket AWS Type Integration */ -export class WebSocketAwsIntegration extends WebSocketTwoWayRouteIntegration { +export class WebSocketAwsIntegration extends CustomResponseWebSocketRoute { /** * @param id id of the underlying integration construct */ diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts index fe5909e4d2da6..d5879a19058d9 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts @@ -2,7 +2,7 @@ import { WebSocketIntegrationType, WebSocketRouteIntegrationConfig, WebSocketRouteIntegrationBindOptions, - WebSocketTwoWayRouteIntegration, + CustomResponseWebSocketRoute, InternalWebSocketIntegrationResponseProps, } from '../../../aws-apigatewayv2'; @@ -41,7 +41,7 @@ export interface WebSocketMockIntegrationProps { /** * Mock WebSocket Integration */ -export class WebSocketMockIntegration extends WebSocketTwoWayRouteIntegration { +export class WebSocketMockIntegration extends CustomResponseWebSocketRoute { /** * @param id id of the underlying integration construct diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts index ed1e7cd640432..f64cdf060e0b3 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts @@ -1,5 +1,5 @@ import { Match, Template } from '../../../assertions'; -import { ContentHandling, InternalWebSocketIntegrationResponseProps, WebSocketApi, WebSocketIntegration, WebSocketIntegrationResponse, WebSocketIntegrationResponseKey, WebSocketIntegrationType, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, WebSocketTwoWayRouteIntegration } from '../../../aws-apigatewayv2'; +import { ContentHandling, InternalWebSocketIntegrationResponseProps, WebSocketApi, WebSocketIntegration, WebSocketIntegrationResponse, WebSocketIntegrationResponseKey, WebSocketIntegrationType, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, CustomResponseWebSocketRoute } from '../../../aws-apigatewayv2'; import * as iam from '../../../aws-iam'; import { Stack } from '../../../core'; @@ -18,7 +18,7 @@ interface WebSocketTestRouteIntegrationConfig { readonly responses?: InternalWebSocketIntegrationResponseProps[]; } -class WebSocketTestIntegration extends WebSocketTwoWayRouteIntegration { +class WebSocketTestIntegration extends CustomResponseWebSocketRoute { constructor(id: string, private readonly props: WebSocketTestRouteIntegrationConfig) { super(id); } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts index 0b22350333489..4e5c644905801 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts @@ -285,9 +285,10 @@ export abstract class WebSocketRouteIntegration { } /** - * The abstract class that all two-way communication route integration classes will implement. + * The abstract class that two-way communication route integration classes + * with customized responses will implement. */ -export abstract class WebSocketTwoWayRouteIntegration extends WebSocketRouteIntegration { +export abstract class CustomResponseWebSocketRoute extends WebSocketRouteIntegration { private responses: InternalWebSocketIntegrationResponseProps[] = []; /** From 71fe931322718cfbef67201d7209e232b73ec9de Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Tue, 2 Apr 2024 12:17:20 +0200 Subject: [PATCH 22/39] chore: add $input integ/example --- ...nteg-aws-websocket-integration.assets.json | 4 ++-- ...eg-aws-websocket-integration.template.json | 6 ++--- .../integ.aws.js.snapshot/manifest.json | 2 +- .../websocket/integ.aws.js.snapshot/tree.json | 6 ++--- .../test/websocket/integ.aws.ts | 22 +++++++++++++++---- .../aws-apigatewayv2-integrations/README.md | 10 +++++---- 6 files changed, 33 insertions(+), 17 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json index 321a67bc33df3..f834a3d122d6a 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json @@ -1,7 +1,7 @@ { "version": "36.0.0", "files": { - "af961815613a0b82b08a2c9890c91a8b84c357304dee79cb9407cecbe52f867f": { + "4cfebf4b7a0bf9c38f92436e41d550e7f7df52d1f8306d5efcfbde8fc674be94": { "source": { "path": "integ-aws-websocket-integration.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "af961815613a0b82b08a2c9890c91a8b84c357304dee79cb9407cecbe52f867f.json", + "objectKey": "4cfebf4b7a0bf9c38f92436e41d550e7f7df52d1f8306d5efcfbde8fc674be94.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json index 9b860e89127ef..d5008d0b043f7 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json @@ -136,11 +136,11 @@ "Fn::Join": [ "", [ - "{\n \"TableName\": \"", + "{\n\"TableName\": \"", { "Ref": "MyTable794EDED1" }, - "\",\n \"Item\": {\n \"id\": { \"S\": \"$context.requestId\" },\n \"userData\": { \"S\": $input.json('$.data') }\n }\n }" + "\",\n\"Item\": {\n\"id\": { \"S\": \"$context.requestId\" },\n\"userData\": { \"S\": $input.json('$.data') }\n}\n}" ] ] } @@ -175,7 +175,7 @@ }, "IntegrationResponseKey": "/4\\d{2}/", "ResponseTemplates": { - "application/json": "{\"error\":\"Bad request\"}" + "application/json": "{\n\"error\": \"Bad request\",\n\"message\": $input.json('$.Message')\n}" } } }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json index 207f2fc127612..d6c541468622e 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/af961815613a0b82b08a2c9890c91a8b84c357304dee79cb9407cecbe52f867f.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/4cfebf4b7a0bf9c38f92436e41d550e7f7df52d1f8306d5efcfbde8fc674be94.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json index 761716931a842..23da0b83bea28 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json @@ -261,11 +261,11 @@ "Fn::Join": [ "", [ - "{\n \"TableName\": \"", + "{\n\"TableName\": \"", { "Ref": "MyTable794EDED1" }, - "\",\n \"Item\": {\n \"id\": { \"S\": \"$context.requestId\" },\n \"userData\": { \"S\": $input.json('$.data') }\n }\n }" + "\",\n\"Item\": {\n\"id\": { \"S\": \"$context.requestId\" },\n\"userData\": { \"S\": $input.json('$.data') }\n}\n}" ] ] } @@ -336,7 +336,7 @@ }, "integrationResponseKey": "/4\\d{2}/", "responseTemplates": { - "application/json": "{\"error\":\"Bad request\"}" + "application/json": "{\n\"error\": \"Bad request\",\n\"message\": $input.json('$.Message')\n}" } } }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.ts index 4e09b0214fdb8..f1988782b2df2 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.ts @@ -8,9 +8,12 @@ import { IntegTest } from '@aws-cdk/integ-tests-alpha'; /* * Stack verification steps: * 1. Connect: 'wscat -c '. Should connect successfully - * 2. Sending: '> {"action":"putItem", "data": "valid"}' should return {success: true} + * 2. Sending: '> {"action":"putItem", "data": "valid"}' should return + * '< {"success": true}' * and add an item to the table, with the userData field set to "valid" - * 2. Sending: '> {"action":"putItem", "data": 1}' should return {error: "Bad request"} and not insert an item to the table + * 3. Sending: '> {"action":"putItem", "data": 1}' should return + * '< {"error": "Bad request", "message": "NUMBER_VALUE cannot be converted to String"}' + * and not insert an item to the table */ const app = new App(); @@ -49,7 +52,7 @@ webSocketApi.addRoute('putItem', { integrationMethod: HttpMethod.POST, credentialsRole: apiRole, requestTemplates: { - $default: `{ + $default: json`{ "TableName": "${table.tableName}", "Item": { "id": { "S": "$context.requestId" }, @@ -67,7 +70,11 @@ webSocketApi.addRoute('putItem', { { responseKey: WebSocketIntegrationResponseKey.clientError, responseTemplates: { - 'application/json': JSON.stringify({ error: 'Bad request' }), + 'application/json': + json`{ + "error": "Bad request", + "message": $input.json('$.Message') + }`, }, }, ], @@ -90,3 +97,10 @@ new IntegTest(app, 'apigatewayv2-aws-integration-integ-test', { }, }, }); + +// remove indentation +function json(inputs: TemplateStringsArray, ...variables: string[]) { + return inputs + .map((input, index) => input + (variables[index] ?? '')).join('') + .split('\n').map((line) => line.trim()).join('\n'); +} \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md index 6d48e32e62863..c47961fe26d18 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md @@ -317,6 +317,8 @@ You can set up your integrations to send responses to your WebSocket client, usi Additionally, some integrations allow you to manipulate and customize your responses, mapped by HTTP response code. This can be done via the `responses` field, or the `addResponse` method. See [Setting up a WebSocket API integration responses in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html). +Similarly to `requestTemplates`, `responseTemplates` can use context or response dependant variables. In the case of an integration response, `$input` will be replaced by the response contents. See [API Gateway WebSocket API mapping template reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html) + ```ts import { WebSocketAwsIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations'; @@ -344,10 +346,10 @@ integration.addResponse({ // You can also create custom response integrations for specific status codes responseKey: apigwv2.WebSocketIntegrationResponseKey.fromStatusCode(404), responseTemplates: { - 'application/json': JSON.stringify({ - error: 'Not found', - requestId: '$context.requestId', - }), + 'application/json': `{ + "error": "Not found", + "message": $input.json('$.Message') + }`, }, }); From 4863001eb51fd154c5cc5091f4114ec2d20a3df2 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Tue, 2 Apr 2024 13:20:57 +0200 Subject: [PATCH 23/39] chore: docs --- .../aws-apigatewayv2-integrations/lib/websocket/aws.ts | 1 + .../aws-apigatewayv2-integrations/lib/websocket/mock.ts | 1 + .../aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts index dc1146faecd80..f9bd90ee6461c 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/aws.ts @@ -57,6 +57,7 @@ export interface WebSocketAwsIntegrationProps { * ``` * * @default - No request template provided to the integration. + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html */ readonly requestTemplates?: { [contentType: string]: string }; diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts index d5879a19058d9..8a842ee1c67f5 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts @@ -19,6 +19,7 @@ export interface WebSocketMockIntegrationProps { * ``` * * @default - No request template provided to the integration. + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html */ readonly requestTemplates?: { [contentType: string]: string }; diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts index 4e5c644905801..802c50f2d7aab 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts @@ -131,6 +131,7 @@ export interface WebSocketIntegrationProps { * ``` * * @default - No request templates required. + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html */ readonly requestTemplates?: { [contentType: string]: string }; From 3f0686a499f4f7fffc04cce8a9fad9cae0a212ee Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Mon, 10 Jun 2024 16:41:11 +0200 Subject: [PATCH 24/39] docs: wording Co-authored-by: paulhcsun <47882901+paulhcsun@users.noreply.github.com> --- packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md index c47961fe26d18..3f96be96578ad 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md @@ -317,7 +317,7 @@ You can set up your integrations to send responses to your WebSocket client, usi Additionally, some integrations allow you to manipulate and customize your responses, mapped by HTTP response code. This can be done via the `responses` field, or the `addResponse` method. See [Setting up a WebSocket API integration responses in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html). -Similarly to `requestTemplates`, `responseTemplates` can use context or response dependant variables. In the case of an integration response, `$input` will be replaced by the response contents. See [API Gateway WebSocket API mapping template reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html) +Similar to `requestTemplates`, `responseTemplates` can use context or response dependant variables. In the case of an integration response, `$input` will be replaced by the response contents. See [API Gateway WebSocket API mapping template reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html) ```ts import { WebSocketAwsIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations'; From 0774e885e7f6441947e879e5b79680f1a2e49899 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Mon, 10 Jun 2024 16:41:32 +0200 Subject: [PATCH 25/39] docs: punctuation Co-authored-by: paulhcsun <47882901+paulhcsun@users.noreply.github.com> --- packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md index 3f96be96578ad..084634a4462e7 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md @@ -312,7 +312,7 @@ See [Working with binary media types for WebSocket APIs](https://docs.aws.amazon ### Integration Responses -You can set up your integrations to send responses to your WebSocket client, using the [`returnResponse`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigatewayv2.WebSocketRouteOptions.html#returnresponse) field. +You can set up your integrations to send responses to your WebSocket client using the [`returnResponse`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigatewayv2.WebSocketRouteOptions.html#returnresponse) field. Additionally, some integrations allow you to manipulate and customize your responses, mapped by HTTP response code. This can be done via the `responses` field, or the `addResponse` method. See [Setting up a WebSocket API integration responses in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html). From 7e0b76d42eaef21abffcf9bf27da3bc81aba11cc Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Mon, 10 Jun 2024 16:41:48 +0200 Subject: [PATCH 26/39] docs: punctuation Co-authored-by: paulhcsun <47882901+paulhcsun@users.noreply.github.com> --- packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md index 084634a4462e7..5ffaeec7ed0ad 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md @@ -314,7 +314,7 @@ See [Working with binary media types for WebSocket APIs](https://docs.aws.amazon You can set up your integrations to send responses to your WebSocket client using the [`returnResponse`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigatewayv2.WebSocketRouteOptions.html#returnresponse) field. -Additionally, some integrations allow you to manipulate and customize your responses, mapped by HTTP response code. This can be done via the `responses` field, or the `addResponse` method. +Additionally, some integrations allow you to manipulate and customize your responses, mapped by HTTP response code. This can be done via the `responses` field or the `addResponse` method. See [Setting up a WebSocket API integration responses in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html). Similar to `requestTemplates`, `responseTemplates` can use context or response dependant variables. In the case of an integration response, `$input` will be replaced by the response contents. See [API Gateway WebSocket API mapping template reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html) From cdc120da119810e0e5604231f0d19c9e7161b8a0 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Mon, 10 Jun 2024 16:43:08 +0200 Subject: [PATCH 27/39] chore: missing new line --- .../aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts index 802c50f2d7aab..d227b7eb124a3 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts @@ -436,4 +436,4 @@ export interface WebSocketRouteIntegrationConfig { function slugify(x: string): string { return x.replace(/[^a-zA-Z0-9]/g, ''); -} \ No newline at end of file +} From 99cdd681c79ff8be26c02cd82f99d0724fe5203b Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Mon, 10 Jun 2024 16:50:25 +0200 Subject: [PATCH 28/39] docs: fix fromStatusRegExp example --- .../lib/websocket/integration-response.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index 1a9782e62d777..bbf5977e84459 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -45,11 +45,12 @@ export class WebSocketIntegrationResponseKey { * Generate an integration response key from a regular expression matching HTTP status codes * * @example - * // Match all 20x status codes - * apigwv2.WebSocketIntegrationResponseKey.fromStatusRegExp('20\\d') + * // Match all HTTP client and server error status codes + * apigwv2.WebSocketIntegrationResponseKey.fromStatusRegExp('4\\d{2}|5\\d{2}') * - * // Match all 4xx status codes, using RegExp - * apigwv2.WebSocketIntegrationResponseKey.fromStatusRegExp(/4\d{2}/.source) + * // Match all HTTP client and server error status codes, + * // using the RegExp built-in object + * apigwv2.WebSocketIntegrationResponseKey.fromStatusRegExp(/4\d{2}|5\d{2}/.source) * * @param httpStatusRegExpStr HTTP status code regular expression string representation * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp From b64002fe5e8b84baed8c98dacfa682e593dcd05a Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Fri, 21 Jun 2024 23:07:47 +0200 Subject: [PATCH 29/39] docs: grammar Co-authored-by: Kendra Neil <53584728+TheRealAmazonKendra@users.noreply.github.com> --- packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md index ea548aa55d7fb..a9f1cc95c4f58 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md @@ -320,7 +320,7 @@ You can set up your integrations to send responses to your WebSocket client usin Additionally, some integrations allow you to manipulate and customize your responses, mapped by HTTP response code. This can be done via the `responses` field or the `addResponse` method. See [Setting up a WebSocket API integration responses in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html). -Similar to `requestTemplates`, `responseTemplates` can use context or response dependant variables. In the case of an integration response, `$input` will be replaced by the response contents. See [API Gateway WebSocket API mapping template reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html) +Similar to `requestTemplates`, `responseTemplates` can use context or response dependent variables. In the case of an integration response, `$input` will be replaced by the response contents. See [API Gateway WebSocket API mapping template reference](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html) ```ts import { WebSocketAwsIntegration } from 'aws-cdk-lib/aws-apigatewayv2-integrations'; From 279bac01ef1af97cd924beab8c1d39148ea4ee8e Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sat, 22 Jun 2024 09:18:34 +0200 Subject: [PATCH 30/39] revert: extra Mock props --- ...teg-mock-websocket-integration.assets.json | 4 ++-- ...g-mock-websocket-integration.template.json | 5 +---- .../integ.mock.js.snapshot/manifest.json | 2 +- .../integ.mock.js.snapshot/tree.json | 5 +---- .../test/websocket/integ.mock.ts | 3 +-- .../lib/websocket/mock.ts | 22 ------------------- .../test/websocket/mock.test.ts | 4 ---- 7 files changed, 6 insertions(+), 39 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json index 13299fa1bc91a..4261af16b4ef5 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json @@ -1,7 +1,7 @@ { "version": "36.0.0", "files": { - "7cf95007ced5671e325821ac7178a5c0ede1f7caaa2ac8e08bff1eb3b4ea7883": { + "43b6f9a29d4e912c98de61b419a73a4a8b88a1d03f0bbd785122e0c5f6b32aea": { "source": { "path": "integ-mock-websocket-integration.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "7cf95007ced5671e325821ac7178a5c0ede1f7caaa2ac8e08bff1eb3b4ea7883.json", + "objectKey": "43b6f9a29d4e912c98de61b419a73a4a8b88a1d03f0bbd785122e0c5f6b32aea.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json index b52fd9d280727..51a592cbe9f77 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json @@ -46,10 +46,7 @@ "Ref": "mywsapi32E6CE11" }, "IntegrationType": "MOCK", - "IntegrationUri": "", - "RequestTemplates": { - "application/json": "{\"statusCode\":200}" - } + "IntegrationUri": "" } }, "mywsapisendmessageRouteintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse72380ED8": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json index c3c9699e24543..25862a172ce45 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/7cf95007ced5671e325821ac7178a5c0ede1f7caaa2ac8e08bff1eb3b4ea7883.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/43b6f9a29d4e912c98de61b419a73a4a8b88a1d03f0bbd785122e0c5f6b32aea.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json index b86683bc63893..b339693889830 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json @@ -113,10 +113,7 @@ "Ref": "mywsapi32E6CE11" }, "integrationType": "MOCK", - "integrationUri": "", - "requestTemplates": { - "application/json": "{\"statusCode\":200}" - } + "integrationUri": "" } }, "constructInfo": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.ts index f3ef371d4a967..e31f0fc02ead6 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.ts @@ -23,7 +23,6 @@ const stage = new WebSocketStage(stack, 'mystage', { webSocketApi.addRoute('sendmessage', { integration: new WebSocketMockIntegration('DefaultIntegration', { - requestTemplates: { 'application/json': JSON.stringify({ statusCode: 200 }) }, responses: [ { responseKey: WebSocketIntegrationResponseKey.default, @@ -40,4 +39,4 @@ new CfnOutput(stack, 'ApiEndpoint', { value: stage.url }); new IntegTest(app, 'apigatewayv2-mock-integration-integ-test', { testCases: [stack], -}); \ No newline at end of file +}); diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts index 8a842ee1c67f5..84970d7fc8942 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/lib/websocket/mock.ts @@ -10,26 +10,6 @@ import { * Props for Mock type integration for a WebSocket Api. */ export interface WebSocketMockIntegrationProps { - /** - * A map of Apache Velocity templates that are applied on the request - * payload. - * - * ``` - * { "application/json": "{ \"statusCode\": 200 }" } - * ``` - * - * @default - No request template provided to the integration. - * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-mapping-template-reference.html - */ - readonly requestTemplates?: { [contentType: string]: string }; - - /** - * The template selection expression for the integration. - * - * @default - No template selection expression provided. - */ - readonly templateSelectionExpression?: string; - /** * Integration responses configuration * @@ -56,8 +36,6 @@ export class WebSocketMockIntegration extends CustomResponseWebSocketRoute { return { type: WebSocketIntegrationType.MOCK, uri: '', - requestTemplates: this.props.requestTemplates, - templateSelectionExpression: this.props.templateSelectionExpression, responses: this.props.responses, }; } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts index 713e9f337e31a..70e0366efbb3a 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/mock.test.ts @@ -28,8 +28,6 @@ describe('MockWebSocketIntegration', () => { new WebSocketApi(stack, 'Api', { defaultRouteOptions: { integration: new WebSocketMockIntegration('DefaultIntegration', { - requestTemplates: { 'application/json': '{ "statusCode": 200 }' }, - templateSelectionExpression: '\\$default', responses: [{ responseKey: WebSocketIntegrationResponseKey.success }], }), returnResponse: true, @@ -40,8 +38,6 @@ describe('MockWebSocketIntegration', () => { Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { IntegrationType: 'MOCK', IntegrationUri: '', - RequestTemplates: { 'application/json': '{ "statusCode": 200 }' }, - TemplateSelectionExpression: '\\$default', }); Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::IntegrationResponse', { ApiId: { Ref: 'ApiF70053CD' }, From 32a2aaf964f917cb5261513857f0dddfe0c29165 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Tue, 25 Jun 2024 15:50:52 +0200 Subject: [PATCH 31/39] fix: restore breaking change, deprecate field --- allowed-breaking-changes.txt | 3 --- .../aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts | 6 ++++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index 65e41d26311ce..26be82ea6a12a 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -933,6 +933,3 @@ removed:aws-cdk-lib.aws_ec2.WindowsVersion.WINDOWS_SERVER_2022_TURKISH_FULL_BASE removed:aws-cdk-lib.aws_ec2.WindowsVersion.WINDOWS_SERVER_2022_TURKISH_FULL_BASE_2023_12_13 removed:aws-cdk-lib.aws_ec2.WindowsVersion.WINDOWS_SERVER_2022_TURKISH_FULL_BASE_2024_01_16 removed:aws-cdk-lib.aws_ec2.WindowsVersion.WINDOWS_SERVER_2022_TURKISH_FULL_BASE_2024_02_14 - -# This member was added but never implemented, and could not have suited a proper implementation -removed:aws-cdk-lib.aws_apigatewayv2.WebSocketRoute.integrationResponseId diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts index 996c85cdf533f..e3e900b92cb1e 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts @@ -75,6 +75,12 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute { public readonly routeId: string; public readonly webSocketApi: IWebSocketApi; public readonly routeKey: string; + /** + * Integration response ID + * + * @deprecated - Use `WebSocketIntegrationResponse` instead + */ + public readonly integrationResponseId?: string; constructor(scope: Construct, id: string, props: WebSocketRouteProps) { super(scope, id); From 747cc08a58c943c227f8028eeba0899cde80d48c Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Tue, 25 Jun 2024 15:51:43 +0200 Subject: [PATCH 32/39] chore: remove nl --- allowed-breaking-changes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/allowed-breaking-changes.txt b/allowed-breaking-changes.txt index 26be82ea6a12a..49d6dffe53dd9 100644 --- a/allowed-breaking-changes.txt +++ b/allowed-breaking-changes.txt @@ -932,4 +932,4 @@ removed:aws-cdk-lib.aws_ec2.WindowsVersion.WINDOWS_SERVER_2022_SWEDISH_FULL_BASE removed:aws-cdk-lib.aws_ec2.WindowsVersion.WINDOWS_SERVER_2022_TURKISH_FULL_BASE_2023_11_15 removed:aws-cdk-lib.aws_ec2.WindowsVersion.WINDOWS_SERVER_2022_TURKISH_FULL_BASE_2023_12_13 removed:aws-cdk-lib.aws_ec2.WindowsVersion.WINDOWS_SERVER_2022_TURKISH_FULL_BASE_2024_01_16 -removed:aws-cdk-lib.aws_ec2.WindowsVersion.WINDOWS_SERVER_2022_TURKISH_FULL_BASE_2024_02_14 +removed:aws-cdk-lib.aws_ec2.WindowsVersion.WINDOWS_SERVER_2022_TURKISH_FULL_BASE_2024_02_14 \ No newline at end of file From f35c28cde9976fa382399aa923624526ef846415 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Tue, 25 Jun 2024 16:08:13 +0200 Subject: [PATCH 33/39] feat: add common status response keys --- .../websocket/integration-response.test.ts | 8 ++++ .../lib/websocket/integration-response.ts | 39 ++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts index f64cdf060e0b3..d4c4f97a122bf 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts @@ -47,6 +47,14 @@ describe('WebSocketIntegrationResponseKey', () => { expect(WebSocketIntegrationResponseKey.success.key).toEqual('/2\\d{2}/'); expect(WebSocketIntegrationResponseKey.clientError.key).toEqual('/4\\d{2}/'); expect(WebSocketIntegrationResponseKey.serverError.key).toEqual('/5\\d{2}/'); + + expect(WebSocketIntegrationResponseKey.ok.key).toEqual('/200/'); + expect(WebSocketIntegrationResponseKey.noContent.key).toEqual('/204/'); + expect(WebSocketIntegrationResponseKey.badRequest.key).toEqual('/400/'); + expect(WebSocketIntegrationResponseKey.unauthorized.key).toEqual('/401/'); + expect(WebSocketIntegrationResponseKey.forbidden.key).toEqual('/403/'); + expect(WebSocketIntegrationResponseKey.notFound.key).toEqual('/404/'); + expect(WebSocketIntegrationResponseKey.internalServerError.key).toEqual('/500/'); }); test('can generate fromStatusCode', () => { diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index bbf5977e84459..b2ab0b08b4f1c 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -27,12 +27,47 @@ export class WebSocketIntegrationResponseKey { */ public static serverError = WebSocketIntegrationResponseKey.fromStatusRegExp(/5\d{2}/.source); + /** + * Match 200 OK status code + */ + public static ok = WebSocketIntegrationResponseKey.fromStatusCode(200); + + /** + * Match 204 Created status code + */ + public static noContent = WebSocketIntegrationResponseKey.fromStatusCode(204); + + /** + * Match 400 Bad Request status code + */ + public static badRequest = WebSocketIntegrationResponseKey.fromStatusCode(400); + + /** + * Match 401 Unauthorized status code + */ + public static unauthorized = WebSocketIntegrationResponseKey.fromStatusCode(401); + + /** + * Match 403 Forbidden status code + */ + public static forbidden = WebSocketIntegrationResponseKey.fromStatusCode(403); + + /** + * Match 404 Not Found status code + */ + public static notFound = WebSocketIntegrationResponseKey.fromStatusCode(404); + + /** + * Match 500 Internal Server Error status code + */ + public static internalServerError = WebSocketIntegrationResponseKey.fromStatusCode(500); + /** * Generate an integration response key from an HTTP status code * * @example - * // Match 403 status code - * apigwv2.WebSocketIntegrationResponseKey.fromStatusCode(403) + * // Match 409 Conflict status code + * apigwv2.WebSocketIntegrationResponseKey.fromStatusCode(409) * * @param httpStatusCode HTTP status code of the mapped response */ From 62ebc9c8d0ae95920ecb278611fd7d0d6822d62e Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Tue, 25 Jun 2024 16:27:39 +0200 Subject: [PATCH 34/39] chore: refactor addResponse params --- .../aws-apigatewayv2-integrations/README.md | 30 +++++++++---------- .../websocket/integration-response.test.ts | 13 ++++---- .../lib/websocket/integration.ts | 11 ++++--- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md index a9f1cc95c4f58..80fa80273b4c9 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/README.md @@ -335,26 +335,26 @@ new apigwv2.WebSocketStage(this, 'mystage', { declare const integration: WebSocketAwsIntegration; // Default response key, will be used if no other matched -integration.addResponse({ responseKey: apigwv2.WebSocketIntegrationResponseKey.default }); +integration.addResponse(apigwv2.WebSocketIntegrationResponseKey.default); -integration.addResponse({ +integration.addResponse( // Success response key, will match all 2xx response HTTP status codes - responseKey: apigwv2.WebSocketIntegrationResponseKey.success, - responseTemplates: { - 'application/json': JSON.stringify({ success: true }), - }, -}); + apigwv2.WebSocketIntegrationResponseKey.success, + { responseTemplates: { 'application/json': JSON.stringify({ success: true }) } }, +); -integration.addResponse({ +integration.addResponse( // You can also create custom response integrations for specific status codes - responseKey: apigwv2.WebSocketIntegrationResponseKey.fromStatusCode(404), - responseTemplates: { - 'application/json': `{ - "error": "Not found", - "message": $input.json('$.Message') - }`, + apigwv2.WebSocketIntegrationResponseKey.fromStatusCode(410), + { + responseTemplates: { + 'application/json': `{ + "error": "Gone", + "message": $input.json('$.Message') + }`, + }, }, -}); +); webSocketApi.addRoute('putItem', { integration, returnResponse: true }); ``` diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts index d4c4f97a122bf..b668598be25ee 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts @@ -34,10 +34,13 @@ class WebSocketTestIntegration extends CustomResponseWebSocketRoute { /** * Add a response to this integration * - * @param response The response to add + * @param responseKey The response key to add + * @param options Optional properties to add to the response */ - addResponse(response: InternalWebSocketIntegrationResponseProps) { - super.addResponse(response); + addResponse( + responseKey: WebSocketIntegrationResponseKey, + options: Omit = {}) { + super.addResponse(responseKey, options); } } @@ -226,7 +229,7 @@ describe('WebSocketIntegrationResponse from properties', () => { { responseKey: WebSocketIntegrationResponseKey.default }, ], }); - integration.addResponse({ responseKey: WebSocketIntegrationResponseKey.clientError }); + integration.addResponse(WebSocketIntegrationResponseKey.clientError); api.addRoute('$default', { integration, returnResponse: true }); @@ -288,7 +291,7 @@ describe('WebSocketIntegrationResponse from properties', () => { { responseKey: WebSocketIntegrationResponseKey.default }, ], }); - integration.addResponse({ responseKey: WebSocketIntegrationResponseKey.default }); + integration.addResponse(WebSocketIntegrationResponseKey.default); // THEN expect( diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts index a56e2556d3369..0131510303c3e 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts @@ -1,6 +1,6 @@ import { Construct } from 'constructs'; import { IWebSocketApi } from './api'; -import { InternalWebSocketIntegrationResponseProps, WebSocketIntegrationResponse } from './integration-response'; +import { InternalWebSocketIntegrationResponseProps, WebSocketIntegrationResponse, WebSocketIntegrationResponseKey } from './integration-response'; import { IWebSocketRoute } from './route'; import { CfnIntegration } from '.././index'; import { IRole } from '../../../aws-iam'; @@ -351,10 +351,13 @@ export abstract class CustomResponseWebSocketRoute extends WebSocketRouteIntegra /** * Add a response to this integration * - * @param response The response to add + * @param responseKey The response key to add + * @param options Optional properties to add to the response */ - addResponse(response: InternalWebSocketIntegrationResponseProps) { - this.responses.push(response); + addResponse( + responseKey: WebSocketIntegrationResponseKey, + options: Omit = {}) { + this.responses.push({ ...options, responseKey }); } } From 4e9a901e1b9ace2ea9b55442d61a3e76ba5803c8 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sat, 6 Jul 2024 10:35:12 +0200 Subject: [PATCH 35/39] fix: jsii build --- .../websocket/integration-response.test.ts | 4 ++-- .../lib/websocket/integration-response.ts | 20 +++++++++++++------ .../lib/websocket/integration.ts | 4 ++-- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts index b668598be25ee..dfa336dffc0b6 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts @@ -1,5 +1,5 @@ import { Match, Template } from '../../../assertions'; -import { ContentHandling, InternalWebSocketIntegrationResponseProps, WebSocketApi, WebSocketIntegration, WebSocketIntegrationResponse, WebSocketIntegrationResponseKey, WebSocketIntegrationType, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, CustomResponseWebSocketRoute } from '../../../aws-apigatewayv2'; +import { ContentHandling, InternalWebSocketIntegrationResponseProps, WebSocketApi, WebSocketIntegrationResponse, WebSocketIntegrationResponseKey, WebSocketIntegrationType, WebSocketRouteIntegrationBindOptions, WebSocketRouteIntegrationConfig, CustomResponseWebSocketRoute, InternalWebSocketIntegrationResponseOptions } from '../../../aws-apigatewayv2'; import * as iam from '../../../aws-iam'; import { Stack } from '../../../core'; @@ -39,7 +39,7 @@ class WebSocketTestIntegration extends CustomResponseWebSocketRoute { */ addResponse( responseKey: WebSocketIntegrationResponseKey, - options: Omit = {}) { + options: InternalWebSocketIntegrationResponseOptions = {}) { super.addResponse(responseKey, options); } } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index b2ab0b08b4f1c..8b6de0953bd8f 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -117,12 +117,7 @@ export class WebSocketIntegrationResponseKey { * * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html */ -export interface InternalWebSocketIntegrationResponseProps { - /** - * The HTTP status code or regular expression the response will be mapped to - */ - readonly responseKey: WebSocketIntegrationResponseKey; - +export interface InternalWebSocketIntegrationResponseOptions { /** * The templates that are used to transform the integration response body. * Specify templates as key-value pairs, with a content type as the key and @@ -166,6 +161,19 @@ export interface InternalWebSocketIntegrationResponseProps { readonly templateSelectionExpression?: string; } +/** + * WebSocket integration response properties, used internally for Integration implementations + * The integration will add itself these props during the bind process + * + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-integration-responses.html + */ +export interface InternalWebSocketIntegrationResponseProps extends InternalWebSocketIntegrationResponseOptions { + /** + * The HTTP status code or regular expression the response will be mapped to + */ + readonly responseKey: WebSocketIntegrationResponseKey; +} + /** * WebSocket integration response properties * diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts index 0131510303c3e..589d91fd014e2 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts @@ -1,6 +1,6 @@ import { Construct } from 'constructs'; import { IWebSocketApi } from './api'; -import { InternalWebSocketIntegrationResponseProps, WebSocketIntegrationResponse, WebSocketIntegrationResponseKey } from './integration-response'; +import { InternalWebSocketIntegrationResponseOptions, InternalWebSocketIntegrationResponseProps, WebSocketIntegrationResponse, WebSocketIntegrationResponseKey } from './integration-response'; import { IWebSocketRoute } from './route'; import { CfnIntegration } from '.././index'; import { IRole } from '../../../aws-iam'; @@ -356,7 +356,7 @@ export abstract class CustomResponseWebSocketRoute extends WebSocketRouteIntegra */ addResponse( responseKey: WebSocketIntegrationResponseKey, - options: Omit = {}) { + options: InternalWebSocketIntegrationResponseOptions = {}) { this.responses.push({ ...options, responseKey }); } } From 5cfbb34e9cb5286aa6d9962dadc0a9e025cada13 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sat, 6 Jul 2024 10:53:58 +0200 Subject: [PATCH 36/39] fix: use uniqueResourceName to generate integ response id --- .../integ-aws-websocket-integration.assets.json | 4 ++-- ...integ-aws-websocket-integration.template.json | 4 ++-- .../integ.aws.js.snapshot/manifest.json | 10 +++++----- .../websocket/integ.aws.js.snapshot/tree.json | 16 ++++++++-------- .../integ-mock-websocket-integration.assets.json | 4 ++-- ...nteg-mock-websocket-integration.template.json | 2 +- .../integ.mock.js.snapshot/manifest.json | 15 ++++++++++++--- .../websocket/integ.mock.js.snapshot/tree.json | 8 ++++---- .../lib/websocket/integration.ts | 5 +++-- 9 files changed, 39 insertions(+), 29 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json index f834a3d122d6a..2eddeda7f15d0 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.assets.json @@ -1,7 +1,7 @@ { "version": "36.0.0", "files": { - "4cfebf4b7a0bf9c38f92436e41d550e7f7df52d1f8306d5efcfbde8fc674be94": { + "53824d3d2f5a61e7b41fae900d4616b5151557c4b2cded050381cb51c5cb8461": { "source": { "path": "integ-aws-websocket-integration.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "4cfebf4b7a0bf9c38f92436e41d550e7f7df52d1f8306d5efcfbde8fc674be94.json", + "objectKey": "53824d3d2f5a61e7b41fae900d4616b5151557c4b2cded050381cb51c5cb8461.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json index d5008d0b043f7..f2b31c274ee0b 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/integ-aws-websocket-integration.template.json @@ -149,7 +149,7 @@ "TimeoutInMillis": 10000 } }, - "mywsapiputItemRouteintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse2F8E9302": { + "mywsapiputItemRoutedefaultintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3FB3E47D1": { "Type": "AWS::ApiGatewayV2::IntegrationResponse", "Properties": { "ApiId": { @@ -164,7 +164,7 @@ } } }, - "mywsapiputItemRouteintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponseF586C3A5": { + "mywsapiputItemRoute4d2integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3F51371BC": { "Type": "AWS::ApiGatewayV2::IntegrationResponse", "Properties": { "ApiId": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json index d6c541468622e..4fd57abacdf13 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/4cfebf4b7a0bf9c38f92436e41d550e7f7df52d1f8306d5efcfbde8fc674be94.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/53824d3d2f5a61e7b41fae900d4616b5151557c4b2cded050381cb51c5cb8461.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -84,16 +84,16 @@ "data": "mywsapiputItemRouteDynamodbPutItem3BF52E5B" } ], - "/integ-aws-websocket-integration/mywsapi/putItem-Route/integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse/Resource": [ + "/integ-aws-websocket-integration/mywsapi/putItem-Route/defaultintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3/Resource": [ { "type": "aws:cdk:logicalId", - "data": "mywsapiputItemRouteintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse2F8E9302" + "data": "mywsapiputItemRoutedefaultintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3FB3E47D1" } ], - "/integ-aws-websocket-integration/mywsapi/putItem-Route/integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponse/Resource": [ + "/integ-aws-websocket-integration/mywsapi/putItem-Route/4d2integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3/Resource": [ { "type": "aws:cdk:logicalId", - "data": "mywsapiputItemRouteintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponseF586C3A5" + "data": "mywsapiputItemRoute4d2integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3F51371BC" } ], "/integ-aws-websocket-integration/mywsapi/putItem-Route/Resource": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json index 23da0b83bea28..49205e9ad9300 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.aws.js.snapshot/tree.json @@ -285,13 +285,13 @@ "version": "0.0.0" } }, - "integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse": { - "id": "integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse", - "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse", + "defaultintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3": { + "id": "defaultintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/defaultintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3", "children": { "Resource": { "id": "Resource", - "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3defaultIntegrationResponse/Resource", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/defaultintegawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::IntegrationResponse", "aws:cdk:cloudformation:props": { @@ -318,13 +318,13 @@ "version": "0.0.0" } }, - "integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponse": { - "id": "integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponse", - "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponse", + "4d2integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3": { + "id": "4d2integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/4d2integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3", "children": { "Resource": { "id": "Resource", - "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE34d2IntegrationResponse/Resource", + "path": "integ-aws-websocket-integration/mywsapi/putItem-Route/4d2integawswebsocketintegrationmywsapiputItemRouteDynamodbPutItem8D520CE3/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::IntegrationResponse", "aws:cdk:cloudformation:props": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json index 4261af16b4ef5..e321dbad7588e 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.assets.json @@ -1,7 +1,7 @@ { "version": "36.0.0", "files": { - "43b6f9a29d4e912c98de61b419a73a4a8b88a1d03f0bbd785122e0c5f6b32aea": { + "7e2a768753bb503ef2a3ff486e3dab6211b724491d63d2690f26df5e2147b3f6": { "source": { "path": "integ-mock-websocket-integration.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "43b6f9a29d4e912c98de61b419a73a4a8b88a1d03f0bbd785122e0c5f6b32aea.json", + "objectKey": "7e2a768753bb503ef2a3ff486e3dab6211b724491d63d2690f26df5e2147b3f6.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json index 51a592cbe9f77..23db96a7cb884 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/integ-mock-websocket-integration.template.json @@ -49,7 +49,7 @@ "IntegrationUri": "" } }, - "mywsapisendmessageRouteintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse72380ED8": { + "mywsapisendmessageRoutedefaultintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D2401A55BEB": { "Type": "AWS::ApiGatewayV2::IntegrationResponse", "Properties": { "ApiId": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json index 25862a172ce45..d07c7b34e3fc3 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/43b6f9a29d4e912c98de61b419a73a4a8b88a1d03f0bbd785122e0c5f6b32aea.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/7e2a768753bb503ef2a3ff486e3dab6211b724491d63d2690f26df5e2147b3f6.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -58,10 +58,10 @@ "data": "mywsapisendmessageRouteDefaultIntegration702159AD" } ], - "/integ-mock-websocket-integration/mywsapi/sendmessage-Route/integmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse/Resource": [ + "/integ-mock-websocket-integration/mywsapi/sendmessage-Route/defaultintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24/Resource": [ { "type": "aws:cdk:logicalId", - "data": "mywsapisendmessageRouteintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse72380ED8" + "data": "mywsapisendmessageRoutedefaultintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D2401A55BEB" } ], "/integ-mock-websocket-integration/mywsapi/sendmessage-Route/Resource": [ @@ -99,6 +99,15 @@ "type": "aws:cdk:logicalId", "data": "CheckBootstrapVersion" } + ], + "mywsapisendmessageRouteintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse72380ED8": [ + { + "type": "aws:cdk:logicalId", + "data": "mywsapisendmessageRouteintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse72380ED8", + "trace": [ + "!!DESTRUCTIVE_CHANGES: WILL_DESTROY" + ] + } ] }, "displayName": "integ-mock-websocket-integration" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json index b339693889830..c5ae8b0d5028f 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-apigatewayv2-integrations/test/websocket/integ.mock.js.snapshot/tree.json @@ -127,13 +127,13 @@ "version": "0.0.0" } }, - "integmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse": { - "id": "integmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse", - "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/integmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse", + "defaultintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24": { + "id": "defaultintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24", + "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/defaultintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24", "children": { "Resource": { "id": "Resource", - "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/integmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24defaultIntegrationResponse/Resource", + "path": "integ-mock-websocket-integration/mywsapi/sendmessage-Route/defaultintegmockwebsocketintegrationmywsapisendmessageRouteDefaultIntegrationA39F8D24/Resource", "attributes": { "aws:cdk:cloudformation:type": "AWS::ApiGatewayV2::IntegrationResponse", "aws:cdk:cloudformation:props": { diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts index 589d91fd014e2..4cc269637ad68 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts @@ -336,10 +336,11 @@ export abstract class CustomResponseWebSocketRoute extends WebSocketRouteIntegra }, {}); for (const responseProps of this.responses) { + const prefix = slugify(responseProps.responseKey.key); + new WebSocketIntegrationResponse( options.scope, - // FIXME any better way to generate a unique id? - Names.nodeUniqueId(this.integration.node) + slugify(responseProps.responseKey.key) + 'IntegrationResponse', + `${prefix}${Names.uniqueResourceName(this.integration, { maxLength: 256 - prefix.length })}`, { ...responseProps, integration: this }, ); } From 0d57e25859248fba95d71333747cedf6c2a727f7 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sat, 6 Jul 2024 11:10:44 +0200 Subject: [PATCH 37/39] docs: fix status description --- .../aws-apigatewayv2/lib/websocket/integration-response.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index 8b6de0953bd8f..008d336e42aaa 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -33,7 +33,7 @@ export class WebSocketIntegrationResponseKey { public static ok = WebSocketIntegrationResponseKey.fromStatusCode(200); /** - * Match 204 Created status code + * Match 204 No Content status code */ public static noContent = WebSocketIntegrationResponseKey.fromStatusCode(204); From a1b0928d7d5583c9bade31be8acce0f21a9cd09f Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sat, 6 Jul 2024 11:15:02 +0200 Subject: [PATCH 38/39] feat: WebSocketIntegrationResponseKey.fromKeys method --- .../websocket/integration-response.test.ts | 21 +++++++++++++++++++ .../lib/websocket/integration-response.ts | 21 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts index dfa336dffc0b6..ac91d91c41bb5 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2-integrations/test/websocket/integration-response.test.ts @@ -68,6 +68,27 @@ describe('WebSocketIntegrationResponseKey', () => { expect(key).toEqual('/404/'); }); + test('can generate fromKeys', () => { + // GIVEN + const { key } = WebSocketIntegrationResponseKey.fromKeys( + WebSocketIntegrationResponseKey.ok, + WebSocketIntegrationResponseKey.fromStatusCode(201), + WebSocketIntegrationResponseKey.clientError, + ); + + // THEN + expect(key).toEqual('/200|201|4\\d{2}/'); + }); + + test('throws is fromKeys includes a non-regex key', () => { + expect( + () => WebSocketIntegrationResponseKey.fromKeys( + WebSocketIntegrationResponseKey.default, + WebSocketIntegrationResponseKey.clientError, + ), + ).toThrow('Cannot use the $default key in a list of keys'); + }); + test('can generate fromStatusRegExp', () => { // GIVEN const { key } = WebSocketIntegrationResponseKey.fromStatusRegExp(/4\d{2}/.source); diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index 008d336e42aaa..6405c991709a3 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -73,7 +73,28 @@ export class WebSocketIntegrationResponseKey { */ public static fromStatusCode(httpStatusCode: number): WebSocketIntegrationResponseKey { return new WebSocketIntegrationResponseKey(`/${httpStatusCode}/`); + } + + /** + * Generate an integration response key from a list of keys + * @param keys keys to generate the key from + * + * @example + * // Match 200 OK, 201 Created, and all 4xx Client Error status codes + * apigwv2.WebSocketIntegrationResponseKey.fromKeys( + * WebSocketIntegrationResponseKey.ok, + * WebSocketIntegrationResponseKey.fromStatusCode(201), + * WebSocketIntegrationResponseKey.clientError + * ) + */ + public static fromKeys(...keys: WebSocketIntegrationResponseKey[]): WebSocketIntegrationResponseKey { + if (keys.includes(WebSocketIntegrationResponseKey.default)) { + throw new Error('Cannot use the $default key in a list of keys'); + } + return WebSocketIntegrationResponseKey.fromStatusRegExp( + keys.map(({ key }) => key.slice(1, -1)).join('|'), + ); } /** From ddbf57bd3bf4f8937a63c94d7188792ed37814b3 Mon Sep 17 00:00:00 2001 From: Jimmy Gaussen Date: Sat, 6 Jul 2024 16:30:56 +0200 Subject: [PATCH 39/39] docs: fix rosetta example --- .../aws-apigatewayv2/lib/websocket/integration-response.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts index 6405c991709a3..dbda6a900ccef 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration-response.ts @@ -82,9 +82,9 @@ export class WebSocketIntegrationResponseKey { * @example * // Match 200 OK, 201 Created, and all 4xx Client Error status codes * apigwv2.WebSocketIntegrationResponseKey.fromKeys( - * WebSocketIntegrationResponseKey.ok, - * WebSocketIntegrationResponseKey.fromStatusCode(201), - * WebSocketIntegrationResponseKey.clientError + * apigwv2.WebSocketIntegrationResponseKey.ok, + * apigwv2.WebSocketIntegrationResponseKey.fromStatusCode(201), + * apigwv2.WebSocketIntegrationResponseKey.clientError * ) */ public static fromKeys(...keys: WebSocketIntegrationResponseKey[]): WebSocketIntegrationResponseKey {