diff --git a/packages/@aws-cdk/aws-apigateway/lib/base-path-mapping.ts b/packages/@aws-cdk/aws-apigateway/lib/base-path-mapping.ts index 066ec229b832f..0df48fcb89e3b 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/base-path-mapping.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/base-path-mapping.ts @@ -56,8 +56,14 @@ export class BasePathMapping extends Resource { super(scope, id); if (props.basePath && !Token.isUnresolved(props.basePath)) { - if (!props.basePath.match(/^[a-zA-Z0-9$_.+!*'()-]+$/)) { - throw new Error(`A base path may only contain letters, numbers, and one of "$-_.+!*'()", received: ${props.basePath}`); + if (props.basePath.startsWith('/') || props.basePath.endsWith('/')) { + throw new Error(`A base path cannot start or end with /", received: ${props.basePath}`); + } + if (props.basePath.match(/\/{2,}/)) { + throw new Error(`A base path cannot have more than one consecutive /", received: ${props.basePath}`); + } + if (!props.basePath.match(/^[a-zA-Z0-9$_.+!*'()-/]+$/)) { + throw new Error(`A base path may only contain letters, numbers, and one of "$-_.+!*'()/", received: ${props.basePath}`); } } diff --git a/packages/@aws-cdk/aws-apigateway/test/base-path-mapping.test.ts b/packages/@aws-cdk/aws-apigateway/test/base-path-mapping.test.ts index c3da93e83ba26..69e7e98d9146f 100644 --- a/packages/@aws-cdk/aws-apigateway/test/base-path-mapping.test.ts +++ b/packages/@aws-cdk/aws-apigateway/test/base-path-mapping.test.ts @@ -8,7 +8,7 @@ describe('BasePathMapping', () => { // GIVEN const stack = new cdk.Stack(); const api = new apigw.RestApi(stack, 'MyApi'); - api.root.addMethod('GET'); // api must have atleast one method. + api.root.addMethod('GET'); // api must have at least one method. const domain = new apigw.DomainName(stack, 'MyDomain', { domainName: 'example.com', certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), @@ -33,7 +33,7 @@ describe('BasePathMapping', () => { // GIVEN const stack = new cdk.Stack(); const api = new apigw.RestApi(stack, 'MyApi'); - api.root.addMethod('GET'); // api must have atleast one method. + api.root.addMethod('GET'); // api must have at least one method. const domain = new apigw.DomainName(stack, 'MyDomain', { domainName: 'example.com', certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), @@ -53,11 +53,11 @@ describe('BasePathMapping', () => { }); }); - test('throw error for invalid basePath property', () => { + test('specify multi-level basePath property', () => { // GIVEN const stack = new cdk.Stack(); const api = new apigw.RestApi(stack, 'MyApi'); - api.root.addMethod('GET'); // api must have atleast one method. + api.root.addMethod('GET'); // api must have at least one method. const domain = new apigw.DomainName(stack, 'MyDomain', { domainName: 'example.com', certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), @@ -65,7 +65,31 @@ describe('BasePathMapping', () => { }); // WHEN - const invalidBasePath = '/invalid-/base-path'; + new apigw.BasePathMapping(stack, 'MyBasePath', { + restApi: api, + domainName: domain, + basePath: 'api/v1/example', + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::ApiGateway::BasePathMapping', { + BasePath: 'api/v1/example', + }); + }); + + test('throws when basePath contains an invalid character', () => { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigw.RestApi(stack, 'MyApi'); + api.root.addMethod('GET'); // api must have at least one method. + const domain = new apigw.DomainName(stack, 'MyDomain', { + domainName: 'example.com', + certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), + endpointType: apigw.EndpointType.REGIONAL, + }); + + // WHEN + const invalidBasePath = 'invalid-/base-path?'; // THEN expect(() => { @@ -77,11 +101,83 @@ describe('BasePathMapping', () => { }).toThrowError(/base path may only contain/); }); + test('throw error for basePath starting with /', () => { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigw.RestApi(stack, 'MyApi'); + api.root.addMethod('GET'); // api must have at least one method. + const domain = new apigw.DomainName(stack, 'MyDomain', { + domainName: 'example.com', + certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), + endpointType: apigw.EndpointType.REGIONAL, + }); + + // WHEN + const invalidBasePath = '/invalid-base-path'; + + // THEN + expect(() => { + new apigw.BasePathMapping(stack, 'MyBasePath', { + restApi: api, + domainName: domain, + basePath: invalidBasePath, + }); + }).toThrowError(/A base path cannot start or end with/); + }); + + test('throw error for basePath ending with /', () => { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigw.RestApi(stack, 'MyApi'); + api.root.addMethod('GET'); // api must have at least one method. + const domain = new apigw.DomainName(stack, 'MyDomain', { + domainName: 'example.com', + certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), + endpointType: apigw.EndpointType.REGIONAL, + }); + + // WHEN + const invalidBasePath = 'invalid-base-path/'; + + // THEN + expect(() => { + new apigw.BasePathMapping(stack, 'MyBasePath', { + restApi: api, + domainName: domain, + basePath: invalidBasePath, + }); + }).toThrowError(/A base path cannot start or end with/); + }); + + test('throw error for basePath containing more than one consecutive /', () => { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigw.RestApi(stack, 'MyApi'); + api.root.addMethod('GET'); // api must have at least one method. + const domain = new apigw.DomainName(stack, 'MyDomain', { + domainName: 'example.com', + certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), + endpointType: apigw.EndpointType.REGIONAL, + }); + + // WHEN + const invalidBasePath = 'in//valid-base-path'; + + // THEN + expect(() => { + new apigw.BasePathMapping(stack, 'MyBasePath', { + restApi: api, + domainName: domain, + basePath: invalidBasePath, + }); + }).toThrowError(/A base path cannot have more than one consecutive \//); + }); + test('specify stage property', () => { // GIVEN const stack = new cdk.Stack(); const api = new apigw.RestApi(stack, 'MyApi'); - api.root.addMethod('GET'); // api must have atleast one method. + api.root.addMethod('GET'); // api must have at least one method. const domain = new apigw.DomainName(stack, 'MyDomain', { domainName: 'example.com', certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), @@ -111,7 +207,7 @@ describe('BasePathMapping', () => { // GIVEN const stack = new cdk.Stack(); const api = new apigw.RestApi(stack, 'MyApi'); - api.root.addMethod('GET'); // api must have atleast one method. + api.root.addMethod('GET'); // api must have at least one method. const domain = new apigw.DomainName(stack, 'MyDomain', { domainName: 'example.com', certificate: acm.Certificate.fromCertificateArn(stack, 'cert', 'arn:aws:acm:us-east-1:1111111:certificate/11-3336f1-44483d-adc7-9cd375c5169d'), diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.js.snapshot/manifest.json b/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.js.snapshot/manifest.json index a57c7ce4a9fcd..0049bafae7b8e 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.js.snapshot/manifest.json @@ -81,6 +81,12 @@ "data": "MappingTwo551C79ED" } ], + "/test-stack/MappingThree/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MappingThree36BBA1B6" + } + ], "/test-stack/BootstrapVersion": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.js.snapshot/test-stack.template.json b/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.js.snapshot/test-stack.template.json index 52d687e8b3f2d..022e83846fdab 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.js.snapshot/test-stack.template.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.js.snapshot/test-stack.template.json @@ -70,6 +70,16 @@ "Ref": "ApiF70053CD" } } + }, + "MappingThree36BBA1B6": { + "Type": "AWS::ApiGateway::BasePathMapping", + "Properties": { + "DomainName": "domainName", + "BasePath": "api/v1/multi-level-path", + "RestApiId": { + "Ref": "ApiF70053CD" + } + } } }, "Outputs": { diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.js.snapshot/tree.json b/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.js.snapshot/tree.json index 2a9baa7dc204d..814e1397b0795 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.js.snapshot/tree.json @@ -217,6 +217,34 @@ "fqn": "@aws-cdk/aws-apigateway.BasePathMapping", "version": "0.0.0" } + }, + "MappingThree": { + "id": "MappingThree", + "path": "test-stack/MappingThree", + "children": { + "Resource": { + "id": "Resource", + "path": "test-stack/MappingThree/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::ApiGateway::BasePathMapping", + "aws:cdk:cloudformation:props": { + "domainName": "domainName", + "basePath": "path", + "restApiId": { + "Ref": "ApiF70053CD" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.CfnBasePathMapping", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-apigateway.BasePathMapping", + "version": "0.0.0" + } } }, "constructInfo": { diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.ts b/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.ts index 9d3a31a3a205e..f25b572545f49 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.ts +++ b/packages/@aws-cdk/aws-apigateway/test/integ.base-path-mapping.ts @@ -27,6 +27,13 @@ export class TestStack extends cdk.Stack { basePath: 'path', attachToStage: false, }); + + new apigateway.BasePathMapping(this, 'MappingThree', { + domainName, + restApi, + basePath: 'api/v1/multi-level-path', + attachToStage: false, + }); } }