Skip to content

Commit

Permalink
Make minimal additions to start adding an API gateway Model type, wit…
Browse files Browse the repository at this point in the history
…h a single test.

Add a test for adding a model with valid JSON schema. Document some test cases.

Add support for optional model name/description properties.

Add test for consumption of a model in a MethodResponse.

Add test case for importing an existing model, and using it.

Clean up testing todo list.

Add test case for exporting a model.

Update formatting from 4 to 2 spaces.

Create IModelConstruct to expose IConstruct properties on concrete implementations of IModel.

Fix tests from rebase to updated master.

Add method to RestApi to direclty add a model.

Tweak some documentation and add an integration test for an API with defined models.

Expose property on model that can be used to generate a canonical reference for another external model to consume.
  • Loading branch information
John Shaskin authored and john-shaskin committed Mar 27, 2019
1 parent 4e105a3 commit 3a4fd4f
Show file tree
Hide file tree
Showing 10 changed files with 84,187 additions and 27 deletions.
60 changes: 59 additions & 1 deletion packages/@aws-cdk/aws-apigateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,64 @@ const proxy = resource.addProxy({
});
```

### Models

You may define models to provide structure to request payloads sent to a method,
and/or payloads returned to a client from a method. A model may be added to a
REST API as follows:

```ts
const beerModel = api.addModel('Beer', 'application/json', {
$schema: 'http://json-schema.org/draft-04/schema#',
title: 'Beer',
type: 'object',
properties: {
name: { type: 'string' },
style: { type: 'string' },
abv: { type: 'number' },
ibu: { type: 'number' },
},
},
'Simple for defining a beer.');

const breweryModel = new apiGateway.Model(this, 'breweryModel', {
restApi: api,
name: 'Brewery',
description: 'Simple model for defining a brewery.',
contentType: 'application/json',
schema: {
$schema: 'http://json-schema.org/draft-04/schema#',
title: 'Brewery',
type: 'object',
properties: {
name: { type: 'string' },
address: { type: 'string' },
beers: { type: 'array', items: { $ref: beerModel.referenceForSchema } }
},
},
});
```

Given a model definition, you may either define a binding to request or response of a method via:

#### Request

RequestModel support not yet implemented.

#### Response

```ts
const method = api.beer.addMethod('GET', getBeerLambdaHandler, {
methodResponses: [{
statusCode: '200',
responseModels: {
'application/json': beerModelJson,
'application/xml': beerModelXml,
}
}]
});
```

### Deployments

By default, the `RestApi` construct will automatically create an API Gateway
Expand Down Expand Up @@ -213,7 +271,7 @@ list of missing features.

### Roadmap

- [ ] Support defining REST API Models [#1695](https://github.com/awslabs/aws-cdk/issues/1695)
- Validate REST API model JSON schema

----

Expand Down
42 changes: 21 additions & 21 deletions packages/@aws-cdk/aws-apigateway/lib/methodresponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@ import { IModel } from './model';

export interface MethodResponse {

/**
* The method response's status code, which you map to an IntegrationResponse.
* Required.
*/
statusCode: string;
/**
* The method response's status code, which you map to an IntegrationResponse.
* Required.
*/
statusCode: string;

/**
* Response parameters that API Gateway sends to the client that called a method.
* Specify response parameters as key-value pairs (string-to-Boolean maps), with
* a destination as the key and a Boolean as the value. Specify the destination
* using the following pattern: method.response.header.name, where the name is a
* valid, unique header name. The Boolean specifies whether a parameter is required.
* @default None
*/
responseParameters?: { [destination: string]: boolean };
/**
* Response parameters that API Gateway sends to the client that called a method.
* Specify response parameters as key-value pairs (string-to-Boolean maps), with
* a destination as the key and a Boolean as the value. Specify the destination
* using the following pattern: method.response.header.name, where the name is a
* valid, unique header name. The Boolean specifies whether a parameter is required.
* @default None
*/
responseParameters?: { [destination: string]: boolean };

/**
* The resources used for the response's content type. Specify response models as
* key-value pairs (string-to-string maps), with a content type as the key and a Model
* resource name as the value.
* @default None
*/
responseModels?: { [contentType: string]: IModel };
/**
* The resources used for the response's content type. Specify response models as
* key-value pairs (string-to-string maps), with a content type as the key and a Model
* resource name as the value.
* @default None
*/
responseModels?: { [contentType: string]: IModel };
}
157 changes: 153 additions & 4 deletions packages/@aws-cdk/aws-apigateway/lib/model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,54 @@
import cdk = require('@aws-cdk/cdk');
import { CfnModel } from './apigateway.generated';
import { IRestApi } from './restapi';

export interface ModelImportProps {
readonly modelId: string;
}

export interface ModelProps {
/**
* The ID of a REST API with which to associate this model.
* Required.
*/
restApi: IRestApi,

/**
* The content type for the model.
* Required.
*/
contentType: string,

/**
* The schema to use to transform data to one or more output formats.
* Specify null ({}) if you don't want to specify a schema.
* Required.
* @see http://json-schema.org/
*/
schema: any, // TODO: Validate that the schema provided matches the JSON schema spec.

/**
* A name for the model. If you don't specify a name, AWS CloudFormation
* generates a unique physical ID and uses that ID for the model name.
* @default None
*
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-name.html
*/
name?: string,

/**
* A description that identifies this model.
* @default None
*/
description?: string,
}

/**
* Base model interface that can be used for a simple text reference or
* extended to creating a model construct.
*/
export interface IModel {
readonly modelId: string;
readonly modelId: string;
}

/**
Expand All @@ -20,7 +69,7 @@ export interface IModel {
* @see https://docs.amazonaws.cn/en_us/apigateway/latest/developerguide/models-mappings.html#models-mappings-models
*/
export class EmptyModel implements IModel {
public readonly modelId = 'Empty';
public readonly modelId = 'Empty';
}

/**
Expand All @@ -40,7 +89,107 @@ export class EmptyModel implements IModel {
* }
*/
export class ErrorModel implements IModel {
public readonly modelId = 'Error';
public readonly modelId = 'Error';
}

/**
* Interface share for creating or importing a model.
*/
export interface IModelConstruct extends cdk.IConstruct, IModel {

}

// TODO: Implement Model, enabling management of custom models.
/**
* Model CFN resource
* {
* "Type" : "AWS::ApiGateway::Model",
* "Properties" : {
* "ContentType" : String, <-- Required
* "Description" : String,
* "Name" : String,
* "RestApiId" : String, <-- Required
* "Schema" : JSON object <-- Required
* }
* }
*/
export class Model extends cdk.Construct implements IModelConstruct {

/**
* Returns a reference to the default Empty model.
*/
public static readonly Empty: IModel = new EmptyModel();

/**
* Returns a reference to the default Error model.
*/
public static readonly Error: IModel = new ErrorModel();

/**
* Import an existing model from another stack
* @param scope
* @param id
* @param props
*/
public static import(scope: cdk.Construct, id: string, props: ModelImportProps): IModelConstruct {
return new ImportedModel(scope, id, props);
}

/**
* The identifier for this model.
*/
public readonly modelId: string;

/**
* The REST API that this model belongs to.
*/
public readonly restApi: IRestApi;

constructor(scope: cdk.Construct, id: string, props: ModelProps) {
super(scope, id);

const model = new CfnModel(this, 'Model', {
restApiId: props.restApi.restApiId,
contentType: props.contentType,
schema: props.schema,
name: props.name,
description: props.description,
});

this.modelId = model.ref;
this.restApi = props.restApi;
}

/**
* Exports a Model resource from this stack.
* @returns Model props that can be imported to another stack.
*/
public export(): ModelImportProps {
return {
modelId: new cdk.CfnOutput(this, 'ModelId', { value: this.modelId }).makeImportValue().toString(),
};
}

/**
* Generates the formatted reference to this model, so that another model can reference it, in its schema
*/
public get referenceForSchema(): string {
return `https://apigateway.amazonaws.com/restapis/${this.restApi.restApiId}/models/${this.modelId}`;
}
}

/**
* Defines a reference to a model created outside of the current CloudFormation stack.
*/
export class ImportedModel extends cdk.Construct implements IModelConstruct {
public readonly modelId: string;

constructor(scope: cdk.Construct, id: string, private readonly props: ModelImportProps) {
super(scope, id);

this.modelId = props.modelId;
}

public export() {
return this.props;
}
}
12 changes: 12 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/restapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CfnAccount, CfnRestApi } from './apigateway.generated';
import { Deployment } from './deployment';
import { Integration } from './integration';
import { Method, MethodOptions } from './method';
import { Model } from './model';
import { IRestApiResource, ResourceBase, ResourceOptions } from './resource';
import { Stage, StageOptions } from './stage';

Expand Down Expand Up @@ -279,6 +280,17 @@ export class RestApi extends cdk.Construct implements IRestApi {
});
}

/**
* Adds a new model resource to the REST API, and returns it
* @param name A name for the model.
* @param contentType The content type for the model.
* @param schema The schema to use to transform data to one or more output formats.
* @param description A description that identifies this model.
*/
public addModel(name: string, contentType: string, schema: any, description?: string): Model {
return new Model(this, name, { restApi: this, contentType, schema, name, description });
}

/**
* Internal API used by `Method` to keep an inventory of methods at the API
* level for validation purposes.
Expand Down
Loading

0 comments on commit 3a4fd4f

Please sign in to comment.