From 1886a8bc7be9c6c283fca4ef3354298b72986fee Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Sun, 18 Jul 2021 08:03:15 +0200 Subject: [PATCH 01/15] Add parameter mapping support, readme update --- .../aws-apigatewayv2-integrations/README.md | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md index cce77fd6398e6..a8838bb44e821 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -21,6 +21,7 @@ - [Lambda Integration](#lambda) - [HTTP Proxy Integration](#http-proxy) - [Private Integration](#private-integration) + - [Request Parameter mapping](#request-parameters) - [WebSocket APIs](#websocket-apis) - [Lambda WebSocket Integration](#lambda-websocket-integration) @@ -149,6 +150,56 @@ const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { }); ``` +### Request parameter mapping + +### Request Parameters + +Request parameter mapping is supported through a set of helper methods. +The following example renames a request header from header1 to header2 by generating mappings +`append:header.header2: $request.header.header1` and `remove:header.header1: ''`: + +```ts +const vpc = new ec2.Vpc(stack, 'VPC'); +const lb = new elbv2.ALB(stack, 'lb', { vpc }); +const listener = lb.addListener('listener', { port: 80 }); +listener.addTargets('target', { + port: 80, +}); + +const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { + defaultIntegration: new HttpAlbIntegration({ + listener, + requestParameters: new RequestParameters() + .addParameter({ + mappingKey: HttpMappingKey.appendHeader('header2'), + mappingValue: MappingValue.requestHeader('header1'), + }) + .addParameter({ + mappingKey: HttpMappingKey.removeHeader(), + mappingValue: MappingValue.NONE, + }), + }), +}); +``` + + + +To use values which does not yet have any helper functions, you can use the custom methods: + +```ts +const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { + defaultIntegration: new HttpAlbIntegration({ + listener, + requestParameters: new RequestParameters() + .addParameter({ + mappingKey: MappingKey.custom('new.header'), + mappingValue: MappingValue.custom('new.value'), + }), + }), +}); +``` + + ## WebSocket APIs WebSocket integrations connect a route to backend resources. The following integrations are supported in the CDK. From 3f2078fb9b777eecd037798f074adccf398e182c Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Thu, 29 Jul 2021 07:40:56 +0200 Subject: [PATCH 02/15] Update packages/@aws-cdk/aws-apigatewayv2-integrations/README.md Co-authored-by: Niranjan Jayakar --- packages/@aws-cdk/aws-apigatewayv2-integrations/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md index a8838bb44e821..a5bc19817d30b 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -150,8 +150,6 @@ const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { }); ``` -### Request parameter mapping - ### Request Parameters Request parameter mapping is supported through a set of helper methods. From 6703f2c7dda0eaaeba414dd3d8b8724a0979467c Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Thu, 29 Jul 2021 07:41:05 +0200 Subject: [PATCH 03/15] Update packages/@aws-cdk/aws-apigatewayv2-integrations/README.md Co-authored-by: Niranjan Jayakar --- packages/@aws-cdk/aws-apigatewayv2-integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md index a5bc19817d30b..56a15abeb4b36 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -21,7 +21,7 @@ - [Lambda Integration](#lambda) - [HTTP Proxy Integration](#http-proxy) - [Private Integration](#private-integration) - - [Request Parameter mapping](#request-parameters) + - [Request Parameters](#request-parameters) - [WebSocket APIs](#websocket-apis) - [Lambda WebSocket Integration](#lambda-websocket-integration) From 1e2c799ff9a3abde2b9a2261ac02e40bd494ed6d Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Thu, 29 Jul 2021 07:41:18 +0200 Subject: [PATCH 04/15] Update packages/@aws-cdk/aws-apigatewayv2-integrations/README.md Co-authored-by: Niranjan Jayakar --- packages/@aws-cdk/aws-apigatewayv2-integrations/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md index 56a15abeb4b36..fae69eaf4a5f7 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -152,6 +152,9 @@ const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { ### Request Parameters +Request parameter mapping allows API requests from clients to be modified before they reach backend integrations. +To use parameter mapping, you specify API request parameters to modify, and specify how to modify the parameters. + Request parameter mapping is supported through a set of helper methods. The following example renames a request header from header1 to header2 by generating mappings `append:header.header2: $request.header.header1` and `remove:header.header1: ''`: From 8b36ecc2ee07822245500f62e8974f5e3f385d2a Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Fri, 30 Jul 2021 06:20:22 +0200 Subject: [PATCH 05/15] Update packages/@aws-cdk/aws-apigatewayv2-integrations/README.md Co-authored-by: Niranjan Jayakar --- packages/@aws-cdk/aws-apigatewayv2-integrations/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md index fae69eaf4a5f7..cfb2e0b71b4a8 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -170,8 +170,9 @@ listener.addTargets('target', { const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { defaultIntegration: new HttpAlbIntegration({ listener, - requestParameters: new RequestParameters() - .addParameter({ + requestParameters: new ParameterMapping() + .appendHeader('header2', MappingValue.header('header1')) + .removeHeader('header1'); mappingKey: HttpMappingKey.appendHeader('header2'), mappingValue: MappingValue.requestHeader('header1'), }) From 0c81a2add48cd1ebbd9521079c06210e3ad06cbc Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Fri, 30 Jul 2021 06:27:43 +0200 Subject: [PATCH 06/15] Fix ParameterMapping example --- packages/@aws-cdk/aws-apigatewayv2-integrations/README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md index cfb2e0b71b4a8..c5b4c68511db6 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -173,12 +173,6 @@ const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { requestParameters: new ParameterMapping() .appendHeader('header2', MappingValue.header('header1')) .removeHeader('header1'); - mappingKey: HttpMappingKey.appendHeader('header2'), - mappingValue: MappingValue.requestHeader('header1'), - }) - .addParameter({ - mappingKey: HttpMappingKey.removeHeader(), - mappingValue: MappingValue.NONE, }), }), }); From b1f6d1591b65ec70b86943c28184fabe37bde1fe Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Thu, 5 Aug 2021 07:32:03 +0200 Subject: [PATCH 07/15] Add first unit test --- .../@aws-cdk/aws-apigatewayv2/lib/http/api.ts | 1 + .../aws-apigatewayv2/lib/http/integration.ts | 59 +++++++++++++++++++ .../aws-apigatewayv2/test/http/route.test.ts | 21 ++++++- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts index e4b6ce929eb2a..8cfc27579fce8 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/api.ts @@ -305,6 +305,7 @@ abstract class HttpApiBase extends ApiBase implements IHttpApi { // note that th connectionType: config.connectionType, payloadFormatVersion: config.payloadFormatVersion, secureServerName: config.secureServerName, + parameterMapping: config.parameterMapping, }); this._integrationCache.saveIntegration(scope, config, integration); diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts index f832b5b7e3b21..c2349c4704ae7 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts @@ -74,6 +74,60 @@ export class PayloadFormatVersion { } } +export interface IMappingKey { + key: string; +}; + +export interface IMappingValue { + value: string; +}; + +export class MappingKey implements IMappingKey { + public static custom(mappingKey: string) {return new MappingKey(mappingKey); } + protected constructor(public readonly key: string) {} +} + +export class HttpMappingKey extends MappingKey { + public static appendHeader(name: string) { return new MappingKey(`append:header.${name}`); } + public static overwriteHeader(name: string) { return new MappingKey(`overwrite:header.${name}`); } + public static removeHeader(name: string) { return new MappingKey(`remove:header.${name}`); } + public static appendQueryString(name: string) {return new MappingKey(`append:querystring.${name}`); } + public static overwriteQueryString(name: string) { return new MappingKey(`overwrite:querystring.${name}`); } + public static removeQueryString(name: string) { return new MappingKey(`remove:querystring.${name}`); } + public static overwritePath() { return new MappingKey('overwrite:path'); } + protected constructor(public readonly k: string) { + super(k); + } +} + +export class MappingValue implements IMappingValue { + public static readonly NONE = new MappingValue(''); + public static requestHeader(name: string) { return new MappingValue(`$request.header.${name}`); } + public static requestQueryString(name: string) { return new MappingValue(`$request.querystring.${name}`); } + public static requestBody(name: string) { return new MappingValue(`$request.body.${name}`); } + public static requestPathFull() {return new MappingValue('$request.path'); } + public static requestPath(name: string) { return new MappingValue(`$request.path.${name}`); } + public static contextVariable(variableName: string) { return new MappingValue(`$context.${variableName}`); } + public static stageVariablesVariable(variableName: string) { return new MappingValue(`$stageVariables.${variableName}`); } + public static custom(value: string) {return new MappingValue(value); } + protected constructor(public readonly value: string) {} +} + +export interface ParameterMappingProps { + readonly key: IMappingKey, + readonly value: IMappingValue, +}; + +export class ParameterMapping { + public readonly mappings: {[key: string]: string} + constructor(params: ParameterMappingProps[]) { + this.mappings = {}; + params.forEach(param => { + this.mappings[param.key.key] = param.value.value; + }); + } +} + /** * The integration properties */ @@ -128,6 +182,8 @@ export interface HttpIntegrationProps { * @default undefined private integration traffic will use HTTP protocol */ readonly secureServerName?: string; + + readonly parameterMapping?: ParameterMapping; } /** @@ -149,6 +205,7 @@ export class HttpIntegration extends Resource implements IHttpIntegration { connectionId: props.connectionId, connectionType: props.connectionType, payloadFormatVersion: props.payloadFormatVersion?.version, + requestParameters: props.parameterMapping?.mappings, }); if (props.secureServerName) { @@ -237,4 +294,6 @@ export interface HttpRouteIntegrationConfig { * @default undefined private integration traffic will use HTTP protocol */ readonly secureServerName?: string; + + readonly parameterMapping?: ParameterMapping; } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts index 748ec8cb68d41..ee54c4aded0f7 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts @@ -1,8 +1,10 @@ +import { SynthUtils } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; import { Stack, App } from '@aws-cdk/core'; import { - HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpConnectionType, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteAuthorizerBindOptions, - HttpRouteAuthorizerConfig, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteAuthorizer, IHttpRouteIntegration, PayloadFormatVersion, + HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpConnectionType, HttpIntegrationType, HttpMappingKey, HttpMethod, HttpRoute, + HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteAuthorizer, IHttpRouteIntegration, + MappingValue, ParameterMapping, PayloadFormatVersion, } from '../../lib'; describe('HttpRoute', () => { @@ -174,6 +176,18 @@ describe('HttpRoute', () => { connectionType: HttpConnectionType.VPC_LINK, uri: 'some-target-arn', secureServerName: 'some-server-name', + parameterMapping: new ParameterMapping( + [ + { + key: HttpMappingKey.appendHeader('header2'), + value: MappingValue.requestHeader('header1'), + }, + { + key: HttpMappingKey.removeHeader('header1'), + value: MappingValue.NONE, + }, + ], + ), }; } } @@ -185,6 +199,9 @@ describe('HttpRoute', () => { routeKey: HttpRouteKey.with('/books', HttpMethod.GET), }); + // eslint-disable-next-line no-console + console.log(JSON.stringify(SynthUtils.synthesize(stack).template, null, 4)); + // THEN expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { IntegrationType: 'HTTP_PROXY', From e5d53741be733cf13328b5834c722934aa76312d Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Thu, 5 Aug 2021 07:34:19 +0200 Subject: [PATCH 08/15] Remove logging --- packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts index ee54c4aded0f7..0a702954fb894 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts @@ -1,4 +1,3 @@ -import { SynthUtils } from '@aws-cdk/assert-internal'; import '@aws-cdk/assert-internal/jest'; import { Stack, App } from '@aws-cdk/core'; import { @@ -199,9 +198,6 @@ describe('HttpRoute', () => { routeKey: HttpRouteKey.with('/books', HttpMethod.GET), }); - // eslint-disable-next-line no-console - console.log(JSON.stringify(SynthUtils.synthesize(stack).template, null, 4)); - // THEN expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { IntegrationType: 'HTTP_PROXY', From fbc06b8d0658e8ef357c1792f57cca4c691a8884 Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Tue, 24 Aug 2021 06:30:50 +0200 Subject: [PATCH 09/15] Simplyfy API --- .../aws-apigatewayv2/lib/http/integration.ts | 72 ++++++++++--------- .../aws-apigatewayv2/test/http/route.test.ts | 58 +++++++++++---- 2 files changed, 85 insertions(+), 45 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts index c2349c4704ae7..685493e7848c9 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts @@ -74,32 +74,10 @@ export class PayloadFormatVersion { } } -export interface IMappingKey { - key: string; -}; - export interface IMappingValue { - value: string; + readonly value: string; }; -export class MappingKey implements IMappingKey { - public static custom(mappingKey: string) {return new MappingKey(mappingKey); } - protected constructor(public readonly key: string) {} -} - -export class HttpMappingKey extends MappingKey { - public static appendHeader(name: string) { return new MappingKey(`append:header.${name}`); } - public static overwriteHeader(name: string) { return new MappingKey(`overwrite:header.${name}`); } - public static removeHeader(name: string) { return new MappingKey(`remove:header.${name}`); } - public static appendQueryString(name: string) {return new MappingKey(`append:querystring.${name}`); } - public static overwriteQueryString(name: string) { return new MappingKey(`overwrite:querystring.${name}`); } - public static removeQueryString(name: string) { return new MappingKey(`remove:querystring.${name}`); } - public static overwritePath() { return new MappingKey('overwrite:path'); } - protected constructor(public readonly k: string) { - super(k); - } -} - export class MappingValue implements IMappingValue { public static readonly NONE = new MappingValue(''); public static requestHeader(name: string) { return new MappingValue(`$request.header.${name}`); } @@ -113,18 +91,48 @@ export class MappingValue implements IMappingValue { protected constructor(public readonly value: string) {} } -export interface ParameterMappingProps { - readonly key: IMappingKey, - readonly value: IMappingValue, -}; - export class ParameterMapping { public readonly mappings: {[key: string]: string} - constructor(params: ParameterMappingProps[]) { + constructor() { this.mappings = {}; - params.forEach(param => { - this.mappings[param.key.key] = param.value.value; - }); + } + + public appendHeader(name: string, value: MappingValue): ParameterMapping { + this.mappings[`append:header.${name}`] = value.value; + return this; + } + + public overwriteHeader(name: string, value: MappingValue): ParameterMapping { + this.mappings[`overwrite:header.${name}`] = value.value; + return this; + } + + public removeHeader(name: string): ParameterMapping { + this.mappings[`remove:header.${name}`] = ''; + return this; + } + + public appendQueryString(name: string, value: MappingValue): ParameterMapping { + this.mappings[`append:querystring.${name}`] = value.value; + return this; + } + public overwriteQueryString(name: string, value: MappingValue): ParameterMapping { + this.mappings[`overwrite:querystring.${name}`] = value.value; + return this; + } + public removeQueryString(name: string): ParameterMapping { + this.mappings[`remove:querystring.${name}`] = ''; + return this; + } + + public overwritePath(value: MappingValue): ParameterMapping { + this.mappings['overwrite:path'] = value.value; + return this; + } + + public custom(key: string, value:string): ParameterMapping { + this.mappings[key] = value; + return this; } } diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts index 0a702954fb894..686305513b2cd 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts @@ -1,7 +1,7 @@ import '@aws-cdk/assert-internal/jest'; import { Stack, App } from '@aws-cdk/core'; import { - HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpConnectionType, HttpIntegrationType, HttpMappingKey, HttpMethod, HttpRoute, + HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpConnectionType, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteAuthorizer, IHttpRouteIntegration, MappingValue, ParameterMapping, PayloadFormatVersion, } from '../../lib'; @@ -175,18 +175,9 @@ describe('HttpRoute', () => { connectionType: HttpConnectionType.VPC_LINK, uri: 'some-target-arn', secureServerName: 'some-server-name', - parameterMapping: new ParameterMapping( - [ - { - key: HttpMappingKey.appendHeader('header2'), - value: MappingValue.requestHeader('header1'), - }, - { - key: HttpMappingKey.removeHeader('header1'), - value: MappingValue.NONE, - }, - ], - ), + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), }; } } @@ -214,6 +205,47 @@ describe('HttpRoute', () => { expect(stack).not.toHaveResource('AWS::ApiGatewayV2::VpcLink'); }); + test('configures private integration correctly when parameter mappings are passed', () => { + // GIVEN + const stack = new Stack(); + const httpApi = new HttpApi(stack, 'HttpApi'); + + class PrivateIntegration implements IHttpRouteIntegration { + public bind(): HttpRouteIntegrationConfig { + return { + method: HttpMethod.ANY, + payloadFormatVersion: PayloadFormatVersion.VERSION_1_0, + type: HttpIntegrationType.HTTP_PROXY, + uri: 'some-target-arn', + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }; + } + } + + // WHEN + new HttpRoute(stack, 'HttpRoute', { + httpApi, + integration: new PrivateIntegration(), + routeKey: HttpRouteKey.with('/books', HttpMethod.GET), + }); + + // THEN + expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + IntegrationMethod: 'ANY', + IntegrationUri: 'some-target-arn', + PayloadFormatVersion: '1.0', + RequestParameters: { + 'append:header.header2': '$request.header.header1', + 'remove:header.header1': '', + }, + }); + + expect(stack).not.toHaveResource('AWS::ApiGatewayV2::VpcLink'); + }); + test('can create route with an authorizer attached', () => { const stack = new Stack(); const httpApi = new HttpApi(stack, 'HttpApi'); From 8856e00f48657d8d07ad212faa5a2d983bfe16a1 Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Sun, 3 Oct 2021 07:33:51 +0200 Subject: [PATCH 10/15] Fix README, move parameter-mapping to separate class --- .../aws-apigatewayv2/lib/parameter-mapping.ts | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 packages/@aws-cdk/aws-apigatewayv2/lib/parameter-mapping.ts diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/parameter-mapping.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/parameter-mapping.ts new file mode 100644 index 0000000000000..97f439ed7a149 --- /dev/null +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/parameter-mapping.ts @@ -0,0 +1,61 @@ +export interface IMappingValue { + readonly value: string; +}; + +export class MappingValue implements IMappingValue { + public static readonly NONE = new MappingValue(''); + public static requestHeader(name: string) { return new MappingValue(`$request.header.${name}`); } + public static requestQueryString(name: string) { return new MappingValue(`$request.querystring.${name}`); } + public static requestBody(name: string) { return new MappingValue(`$request.body.${name}`); } + public static requestPathFull() { return new MappingValue('$request.path'); } + public static requestPath(name: string) { return new MappingValue(`$request.path.${name}`); } + public static contextVariable(variableName: string) { return new MappingValue(`$context.${variableName}`); } + public static stageVariablesVariable(variableName: string) { return new MappingValue(`$stageVariables.${variableName}`); } + public static custom(value: string) { return new MappingValue(value); } + protected constructor(public readonly value: string) { } +} + +export class ParameterMapping { + public readonly mappings: { [key: string]: string } + constructor() { + this.mappings = {}; + } + + public appendHeader(name: string, value: MappingValue): ParameterMapping { + this.mappings[`append:header.${name}`] = value.value; + return this; + } + + public overwriteHeader(name: string, value: MappingValue): ParameterMapping { + this.mappings[`overwrite:header.${name}`] = value.value; + return this; + } + + public removeHeader(name: string): ParameterMapping { + this.mappings[`remove:header.${name}`] = ''; + return this; + } + + public appendQueryString(name: string, value: MappingValue): ParameterMapping { + this.mappings[`append:querystring.${name}`] = value.value; + return this; + } + public overwriteQueryString(name: string, value: MappingValue): ParameterMapping { + this.mappings[`overwrite:querystring.${name}`] = value.value; + return this; + } + public removeQueryString(name: string): ParameterMapping { + this.mappings[`remove:querystring.${name}`] = ''; + return this; + } + + public overwritePath(value: MappingValue): ParameterMapping { + this.mappings['overwrite:path'] = value.value; + return this; + } + + public custom(key: string, value: string): ParameterMapping { + this.mappings[key] = value; + return this; + } +} \ No newline at end of file From ed2c1f30cbba9f7ec66e0aa7ea00bce216074081 Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Sun, 3 Oct 2021 07:34:36 +0200 Subject: [PATCH 11/15] Fix README, move parameter-mapping to separate class --- .../@aws-cdk/aws-apigatewayv2-integrations/README.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md index c5b4c68511db6..0ee3b2c65b640 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -178,18 +178,14 @@ const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { }); ``` - - -To use values which does not yet have any helper functions, you can use the custom methods: +To use keys and values which does not yet have any helper functions, you can use the custom method: ```ts const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { defaultIntegration: new HttpAlbIntegration({ listener, - requestParameters: new RequestParameters() - .addParameter({ - mappingKey: MappingKey.custom('new.header'), - mappingValue: MappingValue.custom('new.value'), + requestParameters: new ParameterMapping() + .custom('myKey', 'myValue'), }), }), }); From 8da39788b99275361084e153576ac78e4ba261d2 Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Mon, 4 Oct 2021 06:37:35 +0200 Subject: [PATCH 12/15] Implementation for higher level constructs, docs update, unit tests --- .../lib/http/alb.ts | 1 + .../lib/http/base-types.ts | 8 ++ .../lib/http/http-proxy.ts | 9 ++ .../lib/http/lambda.ts | 9 ++ .../lib/http/nlb.ts | 1 + .../lib/http/service-discovery.ts | 1 + .../test/http/alb.test.ts | 31 +++++++ .../test/http/http-proxy.test.ts | 24 ++++- .../test/http/lambda.test.ts | 24 ++++- .../test/http/nlb.test.ts | 32 ++++++- .../test/http/service-discovery.test.ts | 36 +++++++- .../aws-apigatewayv2/lib/http/integration.ts | 73 +++------------ .../@aws-cdk/aws-apigatewayv2/lib/index.ts | 1 + .../aws-apigatewayv2/lib/parameter-mapping.ts | 92 ++++++++++++++++++- .../aws-apigatewayv2/test/http/route.test.ts | 8 +- 15 files changed, 277 insertions(+), 73 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/alb.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/alb.ts index 656e0a550408f..e5e6d5c448663 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/alb.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/alb.ts @@ -44,6 +44,7 @@ export class HttpAlbIntegration extends HttpPrivateIntegration { connectionId: vpcLink.vpcLinkId, uri: this.props.listener.listenerArn, secureServerName: this.props.secureServerName, + parameterMapping: this.props.parameterMapping, }; } } diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts index db14e50f7fc54..73e08be73c170 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts @@ -1,4 +1,5 @@ import { HttpMethod, IVpcLink } from '@aws-cdk/aws-apigatewayv2'; +import { ParameterMapping } from '@aws-cdk/aws-apigatewayv2/lib/parameter-mapping'; /** * Base options for private integration @@ -24,4 +25,11 @@ export interface HttpPrivateIntegrationOptions { */ readonly secureServerName?: string; + + /** + * Specifies how to transform HTTP requests before sending them to the backend + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html + * @default undefined requests are sent to the backend unmodified + */ + readonly parameterMapping?: ParameterMapping; } diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts index a7ef2d1b4d7b9..c7f0be8e12b72 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts @@ -6,6 +6,7 @@ import { IHttpRouteIntegration, PayloadFormatVersion, } from '@aws-cdk/aws-apigatewayv2'; +import { ParameterMapping } from '@aws-cdk/aws-apigatewayv2/lib/parameter-mapping'; /** * Properties to initialize a new `HttpProxyIntegration`. @@ -21,6 +22,13 @@ export interface HttpProxyIntegrationProps { * @default HttpMethod.ANY */ readonly method?: HttpMethod; + + /** + * Specifies how to transform HTTP requests before sending them to the backend + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html + * @default undefined requests are sent to the backend unmodified + */ + readonly parameterMapping?: ParameterMapping; } /** @@ -36,6 +44,7 @@ export class HttpProxyIntegration implements IHttpRouteIntegration { payloadFormatVersion: PayloadFormatVersion.VERSION_1_0, // 1.0 is required and is the only supported format type: HttpIntegrationType.HTTP_PROXY, uri: this.props.url, + parameterMapping: this.props.parameterMapping, }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts index 220d3dca57210..5cf8a471e48d9 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts @@ -5,6 +5,7 @@ import { IHttpRouteIntegration, PayloadFormatVersion, } from '@aws-cdk/aws-apigatewayv2'; +import { ParameterMapping } from '@aws-cdk/aws-apigatewayv2/lib/parameter-mapping'; import { ServicePrincipal } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; import { Names, Stack } from '@aws-cdk/core'; @@ -24,6 +25,13 @@ export interface LambdaProxyIntegrationProps { * @default PayloadFormatVersion.VERSION_2_0 */ readonly payloadFormatVersion?: PayloadFormatVersion; + + /** + * Specifies how to transform HTTP requests before sending them to the backend + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html + * @default undefined requests are sent to the backend unmodified + */ + readonly parameterMapping?: ParameterMapping; } /** @@ -50,6 +58,7 @@ export class LambdaProxyIntegration implements IHttpRouteIntegration { type: HttpIntegrationType.LAMBDA_PROXY, uri: this.props.handler.functionArn, payloadFormatVersion: this.props.payloadFormatVersion ?? PayloadFormatVersion.VERSION_2_0, + parameterMapping: this.props.parameterMapping, }; } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/nlb.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/nlb.ts index 1c405b51b3bfd..7aae0aa002354 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/nlb.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/nlb.ts @@ -44,6 +44,7 @@ export class HttpNlbIntegration extends HttpPrivateIntegration { connectionId: vpcLink.vpcLinkId, uri: this.props.listener.listenerArn, secureServerName: this.props.secureServerName, + parameterMapping: this.props.parameterMapping, }; } } diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/service-discovery.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/service-discovery.ts index f9f204b6eba3e..6f3b8eedbea0a 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/service-discovery.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/service-discovery.ts @@ -34,6 +34,7 @@ export class HttpServiceDiscoveryIntegration extends HttpPrivateIntegration { connectionId: this.props.vpcLink.vpcLinkId, uri: this.props.service.serviceArn, secureServerName: this.props.secureServerName, + parameterMapping: this.props.parameterMapping, }; } } diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts index e5871da260bc2..2f4ee0c5ee022 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts @@ -1,5 +1,6 @@ import { Template } from '@aws-cdk/assertions'; import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2'; +import { MappingValue, ParameterMapping } from '@aws-cdk/aws-apigatewayv2/lib/parameter-mapping'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import { Stack } from '@aws-cdk/core'; @@ -143,4 +144,34 @@ describe('HttpAlbIntegration', () => { }, }); }); + + test('parameterMapping option is correctly recognized', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const lb = new elbv2.ApplicationLoadBalancer(stack, 'lb', { vpc }); + const listener = lb.addListener('listener', { port: 80 }); + listener.addTargets('target', { port: 80 }); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpAlbIntegration({ + listener, + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + RequestParameters: { + 'append:header.header2': '$request.header.header1', + 'remove:header.header1': '', + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/http-proxy.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/http-proxy.test.ts index 0c76996fe7867..0f29ec0915fd9 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/http-proxy.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/http-proxy.test.ts @@ -1,5 +1,5 @@ import { Template } from '@aws-cdk/assertions'; -import { HttpApi, HttpIntegration, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteKey, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2'; +import { HttpApi, HttpIntegration, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2'; import { Stack } from '@aws-cdk/core'; import { HttpProxyIntegration } from '../../lib'; @@ -71,4 +71,26 @@ describe('HttpProxyIntegration', () => { IntegrationUri: 'some-target-url', }); }); + + test('parameterMapping is correctly recognized', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + new HttpIntegration(stack, 'HttpInteg', { + httpApi: api, + integrationType: HttpIntegrationType.HTTP_PROXY, + integrationUri: 'some-target-url', + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + IntegrationType: 'HTTP_PROXY', + IntegrationUri: 'some-target-url', + RequestParameters: { + 'append:header.header2': '$request.header.header1', + 'remove:header.header1': '', + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/lambda.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/lambda.test.ts index d0ead43945ec4..85bb624a25d54 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/lambda.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/lambda.test.ts @@ -1,5 +1,5 @@ import { Template } from '@aws-cdk/assertions'; -import { HttpApi, HttpRoute, HttpRouteKey, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2'; +import { HttpApi, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, PayloadFormatVersion } from '@aws-cdk/aws-apigatewayv2'; import { Code, Function, Runtime } from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; import { LambdaProxyIntegration } from '../../lib'; @@ -41,6 +41,28 @@ describe('LambdaProxyIntegration', () => { }); }); + test('parameterMapping selection', () => { + const stack = new Stack(); + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'LambdaProxyRoute', { + httpApi: api, + integration: new LambdaProxyIntegration({ + handler: fooFunction(stack, 'Fn'), + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + RequestParameters: { + 'append:header.header2': '$request.header.header1', + 'remove:header.header1': '', + }, + }); + }); + test('no dependency cycles', () => { const app = new App(); const lambdaStack = new Stack(app, 'lambdaStack'); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/nlb.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/nlb.test.ts index a32d448d8e448..e1e18c43f49aa 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/nlb.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/nlb.test.ts @@ -1,5 +1,5 @@ import { Template } from '@aws-cdk/assertions'; -import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2'; +import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, VpcLink } from '@aws-cdk/aws-apigatewayv2'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import { Stack } from '@aws-cdk/core'; @@ -140,4 +140,34 @@ describe('HttpNlbIntegration', () => { }, }); }); + + test('paramaterMapping option is correctly recognized', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const lb = new elbv2.NetworkLoadBalancer(stack, 'lb', { vpc }); + const listener = lb.addListener('listener', { port: 80 }); + listener.addTargets('target', { port: 80 }); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpNlbIntegration({ + listener, + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + RequestParameters: { + 'append:header.header2': '$request.header.header1', + 'remove:header.header1': '', + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/service-discovery.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/service-discovery.test.ts index 4d3bef328a637..e037004cada0e 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/service-discovery.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/service-discovery.test.ts @@ -1,5 +1,5 @@ import { Template } from '@aws-cdk/assertions'; -import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2'; +import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, MappingValue, ParameterMapping, VpcLink } from '@aws-cdk/aws-apigatewayv2'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as servicediscovery from '@aws-cdk/aws-servicediscovery'; import { Stack } from '@aws-cdk/core'; @@ -125,4 +125,38 @@ describe('HttpServiceDiscoveryIntegration', () => { }, }); }); + + test('parameterMapping option is correctly recognized', () => { + // GIVEN + const stack = new Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const vpcLink = new VpcLink(stack, 'VpcLink', { vpc }); + const namespace = new servicediscovery.PrivateDnsNamespace(stack, 'Namespace', { + name: 'foobar.com', + vpc, + }); + const service = namespace.createService('Service'); + + // WHEN + const api = new HttpApi(stack, 'HttpApi'); + new HttpRoute(stack, 'HttpProxyPrivateRoute', { + httpApi: api, + integration: new HttpServiceDiscoveryIntegration({ + vpcLink, + service, + parameterMapping: new ParameterMapping() + .appendHeader('header2', MappingValue.requestHeader('header1')) + .removeHeader('header1'), + }), + routeKey: HttpRouteKey.with('/pets'), + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { + RequestParameters: { + 'append:header.header2': '$request.header.header1', + 'remove:header.header1': '', + }, + }); + }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts index 685493e7848c9..df0cf84c13da0 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/http/integration.ts @@ -3,6 +3,7 @@ import { Resource } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnIntegration } from '../apigatewayv2.generated'; import { IIntegration } from '../common'; +import { ParameterMapping } from '../parameter-mapping'; import { IHttpApi } from './api'; import { HttpMethod, IHttpRoute } from './route'; @@ -74,68 +75,6 @@ export class PayloadFormatVersion { } } -export interface IMappingValue { - readonly value: string; -}; - -export class MappingValue implements IMappingValue { - public static readonly NONE = new MappingValue(''); - public static requestHeader(name: string) { return new MappingValue(`$request.header.${name}`); } - public static requestQueryString(name: string) { return new MappingValue(`$request.querystring.${name}`); } - public static requestBody(name: string) { return new MappingValue(`$request.body.${name}`); } - public static requestPathFull() {return new MappingValue('$request.path'); } - public static requestPath(name: string) { return new MappingValue(`$request.path.${name}`); } - public static contextVariable(variableName: string) { return new MappingValue(`$context.${variableName}`); } - public static stageVariablesVariable(variableName: string) { return new MappingValue(`$stageVariables.${variableName}`); } - public static custom(value: string) {return new MappingValue(value); } - protected constructor(public readonly value: string) {} -} - -export class ParameterMapping { - public readonly mappings: {[key: string]: string} - constructor() { - this.mappings = {}; - } - - public appendHeader(name: string, value: MappingValue): ParameterMapping { - this.mappings[`append:header.${name}`] = value.value; - return this; - } - - public overwriteHeader(name: string, value: MappingValue): ParameterMapping { - this.mappings[`overwrite:header.${name}`] = value.value; - return this; - } - - public removeHeader(name: string): ParameterMapping { - this.mappings[`remove:header.${name}`] = ''; - return this; - } - - public appendQueryString(name: string, value: MappingValue): ParameterMapping { - this.mappings[`append:querystring.${name}`] = value.value; - return this; - } - public overwriteQueryString(name: string, value: MappingValue): ParameterMapping { - this.mappings[`overwrite:querystring.${name}`] = value.value; - return this; - } - public removeQueryString(name: string): ParameterMapping { - this.mappings[`remove:querystring.${name}`] = ''; - return this; - } - - public overwritePath(value: MappingValue): ParameterMapping { - this.mappings['overwrite:path'] = value.value; - return this; - } - - public custom(key: string, value:string): ParameterMapping { - this.mappings[key] = value; - return this; - } -} - /** * The integration properties */ @@ -191,6 +130,11 @@ export interface HttpIntegrationProps { */ readonly secureServerName?: string; + /** + * Specifies how to transform HTTP requests before sending them to the backend + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html + * @default undefined requests are sent to the backend unmodified + */ readonly parameterMapping?: ParameterMapping; } @@ -303,5 +247,10 @@ export interface HttpRouteIntegrationConfig { */ readonly secureServerName?: string; + /** + * Specifies how to transform HTTP requests before sending them to the backend + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html + * @default undefined requests are sent to the backend unmodified + */ readonly parameterMapping?: ParameterMapping; } diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts index 12dd8113f8b4c..81df171d98aa1 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/index.ts @@ -2,3 +2,4 @@ export * from './apigatewayv2.generated'; export * from './common'; export * from './http'; export * from './websocket'; +export * from './parameter-mapping'; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigatewayv2/lib/parameter-mapping.ts b/packages/@aws-cdk/aws-apigatewayv2/lib/parameter-mapping.ts index 97f439ed7a149..deb967d572de2 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/lib/parameter-mapping.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/lib/parameter-mapping.ts @@ -1,59 +1,143 @@ +/** + * Represents a Mapping Value. + */ export interface IMappingValue { + /** + * Represents a Mapping Value. + */ readonly value: string; }; +/** + * Represents a Mapping Value. + */ export class MappingValue implements IMappingValue { + /** + * Creates an empty mapping value. + */ public static readonly NONE = new MappingValue(''); + + /** + * Creates a header mapping value. + */ public static requestHeader(name: string) { return new MappingValue(`$request.header.${name}`); } + + /** + * Creates a query string mapping value. + */ public static requestQueryString(name: string) { return new MappingValue(`$request.querystring.${name}`); } + + /** + * Creates a request body mapping value. + */ public static requestBody(name: string) { return new MappingValue(`$request.body.${name}`); } - public static requestPathFull() { return new MappingValue('$request.path'); } - public static requestPath(name: string) { return new MappingValue(`$request.path.${name}`); } + + /** + * Creates a request path mapping value. + */ + public static requestPath() { return new MappingValue('$request.path'); } + + /** + * Creates a request path parameter mapping value. + */ + public static requestPathParam(name: string) { return new MappingValue(`$request.path.${name}`); } + + /** + * Creates a context variable mapping value. + */ public static contextVariable(variableName: string) { return new MappingValue(`$context.${variableName}`); } - public static stageVariablesVariable(variableName: string) { return new MappingValue(`$stageVariables.${variableName}`); } + + /** + * Creates a stage variable mapping value. + */ + public static stageVariable(variableName: string) { return new MappingValue(`$stageVariables.${variableName}`); } + + /** + * Creates a custom mapping value. + */ public static custom(value: string) { return new MappingValue(value); } - protected constructor(public readonly value: string) { } + + /** + * Represents a Mapping Value. + */ + public readonly value: string + + protected constructor(value: string) { + this.value = value; + } } +/** + * Represents a Parameter Mapping. + */ export class ParameterMapping { + /** + * Represents all created parameter mappings. + */ public readonly mappings: { [key: string]: string } constructor() { this.mappings = {}; } + /** + * Creates a mapping to append a header. + */ public appendHeader(name: string, value: MappingValue): ParameterMapping { this.mappings[`append:header.${name}`] = value.value; return this; } + /** + * Creates a mapping to overwrite a header. + */ public overwriteHeader(name: string, value: MappingValue): ParameterMapping { this.mappings[`overwrite:header.${name}`] = value.value; return this; } + /** + * Creates a mapping to remove a header. + */ public removeHeader(name: string): ParameterMapping { this.mappings[`remove:header.${name}`] = ''; return this; } + /** + * Creates a mapping to append a query string. + */ public appendQueryString(name: string, value: MappingValue): ParameterMapping { this.mappings[`append:querystring.${name}`] = value.value; return this; } + + /** + * Creates a mapping to overwrite a querystring. + */ public overwriteQueryString(name: string, value: MappingValue): ParameterMapping { this.mappings[`overwrite:querystring.${name}`] = value.value; return this; } + + /** + * Creates a mapping to remove a querystring. + */ public removeQueryString(name: string): ParameterMapping { this.mappings[`remove:querystring.${name}`] = ''; return this; } + /** + * Creates a mapping to overwrite a path. + */ public overwritePath(value: MappingValue): ParameterMapping { this.mappings['overwrite:path'] = value.value; return this; } + /** + * Creates a custom mapping. + */ public custom(key: string, value: string): ParameterMapping { this.mappings[key] = value; return this; diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts index cb4d50f088bc8..75d744b6b5bcc 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/route.test.ts @@ -3,7 +3,9 @@ import { Stack, App } from '@aws-cdk/core'; import { HttpApi, HttpAuthorizer, HttpAuthorizerType, HttpConnectionType, HttpIntegrationType, HttpMethod, HttpRoute, HttpRouteAuthorizerBindOptions, HttpRouteAuthorizerConfig, HttpRouteIntegrationConfig, HttpRouteKey, IHttpRouteAuthorizer, IHttpRouteIntegration, - MappingValue, ParameterMapping, PayloadFormatVersion, + MappingValue, + ParameterMapping, + PayloadFormatVersion, } from '../../lib'; describe('HttpRoute', () => { @@ -232,7 +234,7 @@ describe('HttpRoute', () => { }); // THEN - expect(stack).toHaveResource('AWS::ApiGatewayV2::Integration', { + Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::Integration', { IntegrationType: 'HTTP_PROXY', IntegrationMethod: 'ANY', IntegrationUri: 'some-target-arn', @@ -243,7 +245,7 @@ describe('HttpRoute', () => { }, }); - expect(stack).not.toHaveResource('AWS::ApiGatewayV2::VpcLink'); + Template.fromStack(stack).resourceCountIs('AWS::ApiGatewayV2::VpcLink', 0); }); test('can create route with an authorizer attached', () => { From b3d19630c0d4bc721ff529a516cd9ea7efce0de8 Mon Sep 17 00:00:00 2001 From: Daniel Lindberg Date: Mon, 4 Oct 2021 07:51:26 +0200 Subject: [PATCH 13/15] Fix imports --- .../aws-apigatewayv2-integrations/lib/http/base-types.ts | 3 +-- .../aws-apigatewayv2-integrations/lib/http/http-proxy.ts | 2 +- .../@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts | 2 +- .../aws-apigatewayv2-integrations/test/http/alb.test.ts | 3 +-- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts index 73e08be73c170..1627b9b0c4deb 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/base-types.ts @@ -1,5 +1,4 @@ -import { HttpMethod, IVpcLink } from '@aws-cdk/aws-apigatewayv2'; -import { ParameterMapping } from '@aws-cdk/aws-apigatewayv2/lib/parameter-mapping'; +import { HttpMethod, IVpcLink, ParameterMapping } from '@aws-cdk/aws-apigatewayv2'; /** * Base options for private integration diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts index c7f0be8e12b72..70873c9582fc8 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/http-proxy.ts @@ -4,9 +4,9 @@ import { HttpRouteIntegrationConfig, HttpMethod, IHttpRouteIntegration, + ParameterMapping, PayloadFormatVersion, } from '@aws-cdk/aws-apigatewayv2'; -import { ParameterMapping } from '@aws-cdk/aws-apigatewayv2/lib/parameter-mapping'; /** * Properties to initialize a new `HttpProxyIntegration`. diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts index 5cf8a471e48d9..358263f724bda 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/lib/http/lambda.ts @@ -4,8 +4,8 @@ import { HttpRouteIntegrationConfig, IHttpRouteIntegration, PayloadFormatVersion, + ParameterMapping, } from '@aws-cdk/aws-apigatewayv2'; -import { ParameterMapping } from '@aws-cdk/aws-apigatewayv2/lib/parameter-mapping'; import { ServicePrincipal } from '@aws-cdk/aws-iam'; import { IFunction } from '@aws-cdk/aws-lambda'; import { Names, Stack } from '@aws-cdk/core'; diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts index 2f4ee0c5ee022..3c25e92fe6d21 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/test/http/alb.test.ts @@ -1,6 +1,5 @@ import { Template } from '@aws-cdk/assertions'; -import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink } from '@aws-cdk/aws-apigatewayv2'; -import { MappingValue, ParameterMapping } from '@aws-cdk/aws-apigatewayv2/lib/parameter-mapping'; +import { HttpApi, HttpMethod, HttpRoute, HttpRouteKey, VpcLink, ParameterMapping, MappingValue } from '@aws-cdk/aws-apigatewayv2'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; import { Stack } from '@aws-cdk/core'; From 42ef243264755e9ec6b8f9f8a8a5cf88e922ffb4 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 13 Oct 2021 16:14:22 +0530 Subject: [PATCH 14/15] Apply suggestions from code review --- .../aws-apigatewayv2-integrations/README.md | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md index 0ee3b2c65b640..d0bc59686057b 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -153,23 +153,15 @@ const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { ### Request Parameters Request parameter mapping allows API requests from clients to be modified before they reach backend integrations. -To use parameter mapping, you specify API request parameters to modify, and specify how to modify the parameters. +Parameter mapping can be used to specify modifications to request parameters. See [Transforming API requests and +responses](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-parameter-mapping.html). -Request parameter mapping is supported through a set of helper methods. -The following example renames a request header from header1 to header2 by generating mappings -`append:header.header2: $request.header.header1` and `remove:header.header1: ''`: +The following example creates a new header - `header2` - as a copy of `header1` and removes `header1`. ```ts -const vpc = new ec2.Vpc(stack, 'VPC'); -const lb = new elbv2.ALB(stack, 'lb', { vpc }); -const listener = lb.addListener('listener', { port: 80 }); -listener.addTargets('target', { - port: 80, -}); - const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { defaultIntegration: new HttpAlbIntegration({ - listener, + // ... requestParameters: new ParameterMapping() .appendHeader('header2', MappingValue.header('header1')) .removeHeader('header1'); From 74a4ce1404dac66f246327e0e883592cb56f6853 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Wed, 13 Oct 2021 16:14:39 +0530 Subject: [PATCH 15/15] Apply suggestions from code review --- packages/@aws-cdk/aws-apigatewayv2-integrations/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md index d0bc59686057b..14eb72a56e7a4 100644 --- a/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md +++ b/packages/@aws-cdk/aws-apigatewayv2-integrations/README.md @@ -170,7 +170,7 @@ const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', { }); ``` -To use keys and values which does not yet have any helper functions, you can use the custom method: +To add mapping keys and values not yet supported by the CDK, use the `custom()` method: ```ts const httpEndpoint = new HttpApi(stack, 'HttpProxyPrivateApi', {