From cc2adeb0c767b918201df655bf5ced1515eeb545 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Thu, 15 Jul 2021 09:23:38 -0700 Subject: [PATCH] fix(cfnspec): make EndpointConfiguration of AWS::Serverless::Api a union type (#15526) A recent update to the SAM spec (https://github.com/aws/aws-cdk/pull/15311) changed the EndpointConfiguration property of AWS::Serverless::Api to have a complex type. However, that is a breaking change compared to the previous, string, type. I consulted with the SAM team, and it turns out the property accepts both a string and the complex type. Given that, patch our SAM spec to make EndpointConfiguration a union type. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-sam/test/api.test.ts | 44 +++++++++++++++++++ ...1_SAM_Api_EndpointConfiguration_patch.json | 29 ++++++++++++ .../test/serverless-transform.test.ts | 24 ++++++++++ .../sam/api-endpoint-config-object.yaml | 11 +++++ .../sam/api-endpoint-config-string-empty.yaml | 10 +++++ .../sam/api-endpoint-config-string.yaml | 10 +++++ tools/cfn2ts/lib/codegen.ts | 12 ++++- 7 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 packages/@aws-cdk/aws-sam/test/api.test.ts create mode 100644 packages/@aws-cdk/cfnspec/spec-source/901_SAM_Api_EndpointConfiguration_patch.json create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/sam/api-endpoint-config-object.yaml create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/sam/api-endpoint-config-string-empty.yaml create mode 100644 packages/@aws-cdk/cloudformation-include/test/test-templates/sam/api-endpoint-config-string.yaml diff --git a/packages/@aws-cdk/aws-sam/test/api.test.ts b/packages/@aws-cdk/aws-sam/test/api.test.ts new file mode 100644 index 0000000000000..bed8a90ae4e57 --- /dev/null +++ b/packages/@aws-cdk/aws-sam/test/api.test.ts @@ -0,0 +1,44 @@ +import '@aws-cdk/assert-internal/jest'; +import * as cdk from '@aws-cdk/core'; +import * as sam from '../lib'; + +describe('AWS::Serverless::Api', () => { + let stack: cdk.Stack; + beforeEach(() => { + stack = new cdk.Stack(); + }); + + test('can be created by passing a complex type to EndpointConfiguration', () => { + new sam.CfnApi(stack, 'Api', { + stageName: 'prod', + definitionBody: { + body: 'definitionBody', + }, + endpointConfiguration: { + type: 'GLOBAL', + }, + }); + + expect(stack).toHaveResourceLike('AWS::Serverless::Api', { + StageName: 'prod', + EndpointConfiguration: { + Type: 'GLOBAL', + }, + }); + }); + + test('can be created by passing a string to EndpointConfiguration', () => { + new sam.CfnApi(stack, 'Api', { + stageName: 'prod', + definitionBody: { + body: 'definitionBody', + }, + endpointConfiguration: 'GLOBAL', + }); + + expect(stack).toHaveResourceLike('AWS::Serverless::Api', { + StageName: 'prod', + EndpointConfiguration: 'GLOBAL', + }); + }); +}); diff --git a/packages/@aws-cdk/cfnspec/spec-source/901_SAM_Api_EndpointConfiguration_patch.json b/packages/@aws-cdk/cfnspec/spec-source/901_SAM_Api_EndpointConfiguration_patch.json new file mode 100644 index 0000000000000..906dbbb88466c --- /dev/null +++ b/packages/@aws-cdk/cfnspec/spec-source/901_SAM_Api_EndpointConfiguration_patch.json @@ -0,0 +1,29 @@ +{ + "ResourceTypes": { + "AWS::Serverless::Api": { + "Properties": { + "EndpointConfiguration": { + "patch": { + "description": "Make the EndpointConfiguration property of AWS::Serverless::Api have a union type", + "operations": [ + { + "op": "add", + "path": "/PrimitiveTypes", + "value": ["String"] + }, + { + "op": "add", + "path": "/Types", + "value": ["EndpointConfiguration"] + }, + { + "op": "remove", + "path": "/Type" + } + ] + } + } + } + } + } +} diff --git a/packages/@aws-cdk/cloudformation-include/test/serverless-transform.test.ts b/packages/@aws-cdk/cloudformation-include/test/serverless-transform.test.ts index 853869ec1dd24..ce0d044bb4ed3 100644 --- a/packages/@aws-cdk/cloudformation-include/test/serverless-transform.test.ts +++ b/packages/@aws-cdk/cloudformation-include/test/serverless-transform.test.ts @@ -54,6 +54,30 @@ describe('CDK Include for templates with SAM transform', () => { loadTestFileToJsObject('only-sam-function-policies-array-ddb-crud-if.yaml'), ); }); + + test('can ingest a template with a a union-type property provided as an object, and output it unchanged', () => { + includeTestTemplate(stack, 'api-endpoint-config-object.yaml'); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('api-endpoint-config-object.yaml'), + ); + }); + + test('can ingest a template with a a union-type property provided as a string, and output it unchanged', () => { + includeTestTemplate(stack, 'api-endpoint-config-string.yaml'); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('api-endpoint-config-string.yaml'), + ); + }); + + test('can ingest a template with a a union-type property provided as an empty string, and output it unchanged', () => { + includeTestTemplate(stack, 'api-endpoint-config-string-empty.yaml'); + + expect(stack).toMatchTemplate( + loadTestFileToJsObject('api-endpoint-config-string-empty.yaml'), + ); + }); }); function includeTestTemplate(scope: constructs.Construct, testTemplate: string): inc.CfnInclude { diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/sam/api-endpoint-config-object.yaml b/packages/@aws-cdk/cloudformation-include/test/test-templates/sam/api-endpoint-config-object.yaml new file mode 100644 index 0000000000000..77be57c23823c --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/sam/api-endpoint-config-object.yaml @@ -0,0 +1,11 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Resources: + Api: + Type: AWS::Serverless::Api + Properties: + StageName: prod + DefinitionBody: + Body: DefinitionBody + EndpointConfiguration: + Type: GLOBAL diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/sam/api-endpoint-config-string-empty.yaml b/packages/@aws-cdk/cloudformation-include/test/test-templates/sam/api-endpoint-config-string-empty.yaml new file mode 100644 index 0000000000000..1898e5105a864 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/sam/api-endpoint-config-string-empty.yaml @@ -0,0 +1,10 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Resources: + Api: + Type: AWS::Serverless::Api + Properties: + StageName: prod + DefinitionBody: + Body: DefinitionBody + EndpointConfiguration: '' # empty string diff --git a/packages/@aws-cdk/cloudformation-include/test/test-templates/sam/api-endpoint-config-string.yaml b/packages/@aws-cdk/cloudformation-include/test/test-templates/sam/api-endpoint-config-string.yaml new file mode 100644 index 0000000000000..a12949dabd226 --- /dev/null +++ b/packages/@aws-cdk/cloudformation-include/test/test-templates/sam/api-endpoint-config-string.yaml @@ -0,0 +1,10 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Resources: + Api: + Type: AWS::Serverless::Api + Properties: + StageName: prod + DefinitionBody: + Body: DefinitionBody + EndpointConfiguration: GLOBAL diff --git a/tools/cfn2ts/lib/codegen.ts b/tools/cfn2ts/lib/codegen.ts index f952bb34a832e..6a02f08b1e253 100644 --- a/tools/cfn2ts/lib/codegen.ts +++ b/tools/cfn2ts/lib/codegen.ts @@ -585,9 +585,17 @@ export default class CodeGenerator { this.code.closeBlock(); } - this.code.line('properties = properties || {};'); - this.code.line(`const ret = new ${CFN_PARSE}.FromCloudFormationPropertyObject<${typeName.fqn}>();`); + this.code.line('properties = properties == null ? {} : properties;'); + // if the passed value is not an object, immediately return it, + // and let a validator report an error - + // otherwise, we'll just return an empty object for this case, + // which a validator might not catch + // (if the interface we're emitting this function for has no required properties, for example) + this.code.openBlock("if (typeof properties !== 'object')"); + this.code.line(`return new ${CFN_PARSE}.FromCloudFormationResult(properties);`); + this.code.closeBlock(); + this.code.line(`const ret = new ${CFN_PARSE}.FromCloudFormationPropertyObject<${typeName.fqn}>();`); const self = this; // class used for the visitor class FromCloudFormationFactoryVisitor implements genspec.PropertyVisitor {