From e936e990886d6bff4a42695dff60ee70f536ffd1 Mon Sep 17 00:00:00 2001 From: Niranjan Jayakar Date: Thu, 7 May 2020 12:19:16 +0100 Subject: [PATCH] switch from inline string to inline object. upgraded testing. --- .../aws-apigateway/lib/api-definition.ts | 61 +++++- .../@aws-cdk/aws-apigateway/lib/restapi.ts | 9 +- ... integ.api-definition.asset.expected.json} | 44 ++++- .../test/integ.api-definition.asset.ts | 21 +++ .../integ.api-definition.inline.expected.json | 177 ++++++++++++++++++ .../test/integ.api-definition.inline.ts | 66 +++++++ .../test/integ.restapi.fromdefinition.ts | 13 -- .../test/sample-definition.yaml | 2 +- .../test/test.api-definition.ts | 51 ++++- 9 files changed, 403 insertions(+), 41 deletions(-) rename packages/@aws-cdk/aws-apigateway/test/{integ.restapi.fromdefinition.expected.json => integ.api-definition.asset.expected.json} (67%) create mode 100644 packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.ts create mode 100644 packages/@aws-cdk/aws-apigateway/test/integ.api-definition.inline.expected.json create mode 100644 packages/@aws-cdk/aws-apigateway/test/integ.api-definition.inline.ts delete mode 100644 packages/@aws-cdk/aws-apigateway/test/integ.restapi.fromdefinition.ts diff --git a/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts b/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts index dca45c7fb3792..64eac660619ff 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/api-definition.ts @@ -14,9 +14,52 @@ export abstract class ApiDefinition { } /** - * Creates an API definition from a string + * Create an API definition from an inline object. The inline object must follow the + * schema of OpenAPI 2.0 or OpenAPI 3.0 + * + * @example + * ApiDefinition.fromInline({ + * openapi: '3.0.2', + * paths: { + * '/pets': { + * get: { + * 'responses': { + * 200: { + * content: { + * 'application/json': { + * schema: { + * $ref: '#/components/schemas/Empty', + * }, + * }, + * }, + * }, + * }, + * 'x-amazon-apigateway-integration': { + * responses: { + * default: { + * statusCode: '200', + * }, + * }, + * requestTemplates: { + * 'application/json': '{"statusCode": 200}', + * }, + * passthroughBehavior: 'when_no_match', + * type: 'mock', + * }, + * }, + * }, + * }, + * components: { + * schemas: { + * Empty: { + * title: 'Empty Schema', + * type: 'object', + * }, + * }, + * }, + * }); */ - public static fromInline(definition: string): InlineApiDefinition { + public static fromInline(definition: any): InlineApiDefinition { return new InlineApiDefinition(definition); } @@ -68,7 +111,7 @@ export interface ApiDefinitionConfig { * * @default - API definition is not defined inline */ - readonly inlineDefinition?: string; + readonly inlineDefinition?: any; } /** @@ -99,14 +142,18 @@ export class S3ApiDefinition extends ApiDefinition { } /** - * OpenAPI specification from an inline string. + * OpenAPI specification from an inline JSON object. */ export class InlineApiDefinition extends ApiDefinition { - constructor(private definition: string) { + constructor(private definition: any) { super(); - if (definition.length === 0) { - throw new Error('Inline API definition cannot be empty'); + if (typeof(definition) !== 'object') { + throw new Error('definition should be of type object'); + } + + if (Object.keys(definition).length === 0) { + throw new Error('JSON definition cannot be empty'); } } diff --git a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts index f8339313c44d0..290935693337f 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/restapi.ts @@ -1,7 +1,7 @@ import { IVpcEndpoint } from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import { CfnOutput, Construct, IResource as IResourceBase, Resource, Stack } from '@aws-cdk/core'; -import { ApiDefinition, ApiDefinitionConfig } from './api-definition'; +import { ApiDefinition } from './api-definition'; import { ApiKey, IApiKey } from './api-key'; import { CfnAccount, CfnRestApi } from './apigateway.generated'; import { CorsOptions } from './cors'; @@ -620,10 +620,3 @@ class RootResource extends ResourceBase { } } } - -export function verifyAPIDefinitionConfig(definition: ApiDefinitionConfig) { - // mutually exclusive - if ((!definition.inlineDefinition && !definition.s3Location) || (definition.inlineDefinition && definition.s3Location)) { - throw new Error('APIDefinition must specify one of "inlineDefinition" or "s3Location" but not both'); - } -} diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.fromdefinition.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.expected.json similarity index 67% rename from packages/@aws-cdk/aws-apigateway/test/integ.restapi.fromdefinition.expected.json rename to packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.expected.json index a03d0395259c2..71dd02f17ab9a 100644 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.fromdefinition.expected.json +++ b/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.expected.json @@ -5,7 +5,7 @@ "Properties": { "BodyS3Location": { "Bucket": { - "Ref": "AssetParametersb4e546901387aeeb588eabec043d35a7fdbe4d304185ae7ab763bb3ae4e61d58S3BucketC3882222" + "Ref": "AssetParameters68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fbS3Bucket42039E29" }, "Key": { "Fn::Join": [ @@ -18,7 +18,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersb4e546901387aeeb588eabec043d35a7fdbe4d304185ae7ab763bb3ae4e61d58S3VersionKey1FFB68C8" + "Ref": "AssetParameters68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fbS3VersionKeyB590532F" } ] } @@ -31,7 +31,7 @@ "Fn::Split": [ "||", { - "Ref": "AssetParametersb4e546901387aeeb588eabec043d35a7fdbe4d304185ae7ab763bb3ae4e61d58S3VersionKey1FFB68C8" + "Ref": "AssetParameters68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fbS3VersionKeyB590532F" } ] } @@ -137,20 +137,46 @@ ] ] } + }, + "PetsURL": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "myapi4C7BF186" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "myapiDeploymentStageprod298F01AF" + }, + "/pets" + ] + ] + } } }, "Parameters": { - "AssetParametersb4e546901387aeeb588eabec043d35a7fdbe4d304185ae7ab763bb3ae4e61d58S3BucketC3882222": { + "AssetParameters68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fbS3Bucket42039E29": { "Type": "String", - "Description": "S3 bucket for asset \"b4e546901387aeeb588eabec043d35a7fdbe4d304185ae7ab763bb3ae4e61d58\"" + "Description": "S3 bucket for asset \"68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fb\"" }, - "AssetParametersb4e546901387aeeb588eabec043d35a7fdbe4d304185ae7ab763bb3ae4e61d58S3VersionKey1FFB68C8": { + "AssetParameters68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fbS3VersionKeyB590532F": { "Type": "String", - "Description": "S3 key for asset version \"b4e546901387aeeb588eabec043d35a7fdbe4d304185ae7ab763bb3ae4e61d58\"" + "Description": "S3 key for asset version \"68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fb\"" }, - "AssetParametersb4e546901387aeeb588eabec043d35a7fdbe4d304185ae7ab763bb3ae4e61d58ArtifactHash1A35D17D": { + "AssetParameters68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fbArtifactHashA9C91B6D": { "Type": "String", - "Description": "Artifact hash for asset \"b4e546901387aeeb588eabec043d35a7fdbe4d304185ae7ab763bb3ae4e61d58\"" + "Description": "Artifact hash for asset \"68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fb\"" } } } \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.ts b/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.ts new file mode 100644 index 0000000000000..1b8531ccad8d5 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.asset.ts @@ -0,0 +1,21 @@ +import * as cdk from '@aws-cdk/core'; +import * as path from 'path'; +import * as apigateway from '../lib'; + +/* + * Stack verification steps: + * * `curl -i ` should return HTTP code 200 + */ + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integtest-restapi-fromdefinition-asset'); + +const api = new apigateway.SpecRestApi(stack, 'my-api', { + apiDefinition: apigateway.ApiDefinition.fromAsset(path.join(__dirname, 'sample-definition.yaml')), +}); + +new cdk.CfnOutput(stack, 'PetsURL', { + value: api.urlForPath('/pets'), +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.inline.expected.json b/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.inline.expected.json new file mode 100644 index 0000000000000..3eaae1ff8fd58 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.inline.expected.json @@ -0,0 +1,177 @@ +{ + "Resources": { + "myapi4C7BF186": { + "Type": "AWS::ApiGateway::RestApi", + "Properties": { + "Body": { + "openapi": "3.0.2", + "info": { + "version": "1.0.0", + "title": "Test API for CDK" + }, + "paths": { + "/pets": { + "get": { + "summary": "Test Method", + "operationId": "testMethod", + "responses": { + "200": { + "description": "A paged array of pets", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Empty" + } + } + } + } + }, + "x-amazon-apigateway-integration": { + "responses": { + "default": { + "statusCode": "200" + } + }, + "requestTemplates": { + "application/json": "{\"statusCode\": 200}" + }, + "passthroughBehavior": "when_no_match", + "type": "mock" + } + } + } + }, + "components": { + "schemas": { + "Empty": { + "title": "Empty Schema", + "type": "object" + } + } + } + }, + "Name": "my-api" + } + }, + "myapiDeployment92F2CB49": { + "Type": "AWS::ApiGateway::Deployment", + "Properties": { + "RestApiId": { + "Ref": "myapi4C7BF186" + }, + "Description": "Automatically created by the RestApi construct" + } + }, + "myapiDeploymentStageprod298F01AF": { + "Type": "AWS::ApiGateway::Stage", + "Properties": { + "RestApiId": { + "Ref": "myapi4C7BF186" + }, + "DeploymentId": { + "Ref": "myapiDeployment92F2CB49" + }, + "StageName": "prod" + } + }, + "myapiCloudWatchRole095452E5": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "apigateway.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + ] + } + ] + } + }, + "myapiAccountEC421A0A": { + "Type": "AWS::ApiGateway::Account", + "Properties": { + "CloudWatchRoleArn": { + "Fn::GetAtt": [ + "myapiCloudWatchRole095452E5", + "Arn" + ] + } + }, + "DependsOn": [ + "myapi4C7BF186" + ] + } + }, + "Outputs": { + "myapiEndpoint3628AFE3": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "myapi4C7BF186" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "myapiDeploymentStageprod298F01AF" + }, + "/" + ] + ] + } + }, + "PetsURL": { + "Value": { + "Fn::Join": [ + "", + [ + "https://", + { + "Ref": "myapi4C7BF186" + }, + ".execute-api.", + { + "Ref": "AWS::Region" + }, + ".", + { + "Ref": "AWS::URLSuffix" + }, + "/", + { + "Ref": "myapiDeploymentStageprod298F01AF" + }, + "/pets" + ] + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.inline.ts b/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.inline.ts new file mode 100644 index 0000000000000..4d9f3bcf76364 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/test/integ.api-definition.inline.ts @@ -0,0 +1,66 @@ +import * as cdk from '@aws-cdk/core'; +import * as apigateway from '../lib'; + +/* + * Stack verification steps: + * * `curl -i ` should return HTTP code 200 + */ + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integtest-restapi-fromdefinition-inline'); + +const api = new apigateway.SpecRestApi(stack, 'my-api', { + apiDefinition: apigateway.ApiDefinition.fromInline({ + openapi: '3.0.2', + info: { + version: '1.0.0', + title: 'Test API for CDK', + }, + paths: { + '/pets': { + get: { + 'summary': 'Test Method', + 'operationId': 'testMethod', + 'responses': { + 200: { + description: 'A paged array of pets', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/Empty', + }, + }, + }, + }, + }, + 'x-amazon-apigateway-integration': { + responses: { + default: { + statusCode: '200', + }, + }, + requestTemplates: { + 'application/json': '{"statusCode": 200}', + }, + passthroughBehavior: 'when_no_match', + type: 'mock', + }, + }, + }, + }, + components: { + schemas: { + Empty: { + title: 'Empty Schema', + type: 'object', + }, + }, + }, + }), +}); + +new cdk.CfnOutput(stack, 'PetsURL', { + value: api.urlForPath('/pets'), +}); + +app.synth(); diff --git a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.fromdefinition.ts b/packages/@aws-cdk/aws-apigateway/test/integ.restapi.fromdefinition.ts deleted file mode 100644 index 3abbdc09b14d4..0000000000000 --- a/packages/@aws-cdk/aws-apigateway/test/integ.restapi.fromdefinition.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as cdk from '@aws-cdk/core'; -import * as path from 'path'; -import * as apigateway from '../lib'; - -const app = new cdk.App(); - -const stack = new cdk.Stack(app, 'test-apigateway-restapi-fromdefinition'); - -new apigateway.SpecRestApi(stack, 'my-api', { - apiDefinition: apigateway.ApiDefinition.fromAsset(path.join(__dirname, 'sample-definition.yaml')), -}); - -app.synth(); diff --git a/packages/@aws-cdk/aws-apigateway/test/sample-definition.yaml b/packages/@aws-cdk/aws-apigateway/test/sample-definition.yaml index b40573a0c08b8..a0dd197f67c37 100644 --- a/packages/@aws-cdk/aws-apigateway/test/sample-definition.yaml +++ b/packages/@aws-cdk/aws-apigateway/test/sample-definition.yaml @@ -3,7 +3,7 @@ info: version: 1.0.0 title: Test API for CDK paths: - /testing: + /pets: get: summary: Test Method operationId: testMethod diff --git a/packages/@aws-cdk/aws-apigateway/test/test.api-definition.ts b/packages/@aws-cdk/aws-apigateway/test/test.api-definition.ts index 9c7a01643cdef..7753c83fe47a8 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.api-definition.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.api-definition.ts @@ -1,19 +1,49 @@ +import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; import { Test } from 'nodeunit'; import * as path from 'path'; import * as apigw from '../lib'; export = { - 'apigateway.ApiDefinition.fromInline': { - 'fails if inline definition is empty'(test: Test) { + 'apigateway.ApiDefinition.fromJson': { + 'happy case'(test: Test) { + const stack = new cdk.Stack(); + const definition = { + key1: 'val1', + }; + const config = apigw.ApiDefinition.fromInline(definition).bind(stack); + test.deepEqual(config.inlineDefinition, definition); + test.ok(config.s3Location === undefined); + test.done(); + }, + + 'fails if Json definition is empty'(test: Test) { test.throws( - () => defineRestApi(apigw.ApiDefinition.fromInline('')), + () => defineRestApi(apigw.ApiDefinition.fromInline({})), /cannot be empty/); test.done(); }, + + 'fails if definition is not an object'(test: Test) { + test.throws( + () => defineRestApi(apigw.ApiDefinition.fromInline('not-json')), + /should be of type object/); + test.done(); + }, }, 'apigateway.ApiDefinition.fromAsset': { + 'happy case'(test: Test) { + const stack = new cdk.Stack(); + const config = apigw.ApiDefinition.fromAsset(path.join(__dirname, 'sample-definition.yaml')).bind(stack); + test.ok(config.inlineDefinition === undefined); + test.ok(config.s3Location !== undefined); + test.deepEqual(stack.resolve(config.s3Location!.bucket), { + Ref: 'AssetParameters68497ac876de4e963fc8f7b5f1b28844c18ecc95e3f7c6e9e0bf250e03c037fbS3Bucket42039E29', + }); + test.done(); + }, + 'fails if a directory is given for an asset'(test: Test) { // GIVEN const fileAsset = apigw.ApiDefinition.fromAsset(path.join(__dirname, 'authorizers')); @@ -47,6 +77,21 @@ export = { test.done(); }, }, + + 'apigateway.ApiDefinition.fromBucket': { + 'happy case'(test: Test) { + const stack = new cdk.Stack(); + const bucket = new s3.Bucket(stack, 'my-bucket'); + const config = apigw.ApiDefinition.fromBucket(bucket, 'my-key', 'my-version').bind(stack); + test.ok(config.inlineDefinition === undefined); + test.ok(config.s3Location !== undefined); + test.deepEqual(stack.resolve(config.s3Location!.bucket), { + Ref: 'mybucket15D133BF', + }); + test.equals(config.s3Location!.key, 'my-key'); + test.done(); + }, + }, }; function defineRestApi(definition: apigw.ApiDefinition) {