diff --git a/packages/@aws-cdk/aws-apigateway/README.md b/packages/@aws-cdk/aws-apigateway/README.md index 2de3f833ea457..8297d3062d564 100644 --- a/packages/@aws-cdk/aws-apigateway/README.md +++ b/packages/@aws-cdk/aws-apigateway/README.md @@ -213,6 +213,10 @@ to allow users revert the stage to an old deployment manually. See [awslabs/aws-cdk#723](https://github.com/awslabs/aws-cdk/issues/723) for a list of missing features. +### Roadmap + +- [ ] Support defining REST API Models [#1695](https://github.com/awslabs/aws-cdk/issues/1695) + ---- This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project. \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/lib/index.ts b/packages/@aws-cdk/aws-apigateway/lib/index.ts index 1ccd62b520805..b303ae6f4e243 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/index.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/index.ts @@ -7,6 +7,8 @@ export * from './stage'; export * from './integrations'; export * from './lambda-api'; export * from './vpc-link'; +export * from './methodresponse'; +export * from './model'; // AWS::ApiGateway CloudFormation Resources: export * from './apigateway.generated'; diff --git a/packages/@aws-cdk/aws-apigateway/lib/method.ts b/packages/@aws-cdk/aws-apigateway/lib/method.ts index 1e76106409f23..c4ead637ca3ec 100644 --- a/packages/@aws-cdk/aws-apigateway/lib/method.ts +++ b/packages/@aws-cdk/aws-apigateway/lib/method.ts @@ -2,6 +2,7 @@ import cdk = require('@aws-cdk/cdk'); import { CfnMethod, CfnMethodProps } from './apigateway.generated'; import { ConnectionType, Integration } from './integration'; import { MockIntegration } from './integrations/mock'; +import { MethodResponse } from './methodresponse'; import { IRestApiResource } from './resource'; import { RestApi } from './restapi'; import { validateHttpMethod } from './util'; @@ -34,12 +35,30 @@ export interface MethodOptions { */ apiKeyRequired?: boolean; + /** + * The responses that can be sent to the client who calls the method. + * @default None + * + * This property is not required, but if these are not supplied for a Lambda + * proxy integration, the Lambda function must return a value of the correct format, + * for the integration response to be correctly mapped to a response to the client. + * @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-method-settings-method-response.html + */ + methodResponses?: MethodResponse[] + + /** + * The request parameters that API Gateway accepts. Specify request parameters + * as key-value pairs (string-to-Boolean mapping), with a source as the key and + * a Boolean as the value. The Boolean specifies whether a parameter is required. + * A source must match the format method.request.location.name, where the location + * is querystring, path, or header, and name is a valid, unique parameter name. + * @default None + */ + requestParameters?: { [param: string]: boolean }; + // TODO: // - RequestValidatorId // - RequestModels - // - RequestParameters - // - MethodResponses - requestParameters?: { [param: string]: boolean }; } export interface MethodProps { @@ -93,7 +112,8 @@ export class Method extends cdk.Construct { authorizationType: options.authorizationType || defaultMethodOptions.authorizationType || AuthorizationType.None, authorizerId: options.authorizerId || defaultMethodOptions.authorizerId, requestParameters: options.requestParameters, - integration: this.renderIntegration(props.integration) + integration: this.renderIntegration(props.integration), + methodResponses: this.renderMethodResponses(options.methodResponses), }; const resource = new CfnMethod(this, 'Resource', methodProps); @@ -188,6 +208,34 @@ export class Method extends cdk.Construct { credentials, }; } + + private renderMethodResponses(methodResponses: MethodResponse[] | undefined): CfnMethod.MethodResponseProperty[] | undefined { + if (!methodResponses) { + // Fall back to nothing + return undefined; + } + + return methodResponses.map(mr => { + let responseModels: {[contentType: string]: string} | undefined; + + if (mr.responseModels) { + responseModels = {}; + for (const contentType in mr.responseModels) { + if (mr.responseModels.hasOwnProperty(contentType)) { + responseModels[contentType] = mr.responseModels[contentType].modelId; + } + } + } + + const methodResponseProp = { + statusCode: mr.statusCode, + responseParameters: mr.responseParameters, + responseModels, + }; + + return methodResponseProp; + }); + } } export enum AuthorizationType { diff --git a/packages/@aws-cdk/aws-apigateway/lib/methodresponse.ts b/packages/@aws-cdk/aws-apigateway/lib/methodresponse.ts new file mode 100644 index 0000000000000..59032a06d860f --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/lib/methodresponse.ts @@ -0,0 +1,28 @@ +import { IModel } from './model'; + +export interface MethodResponse { + + /** + * 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 }; + + /** + * 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 }; +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/lib/model.ts b/packages/@aws-cdk/aws-apigateway/lib/model.ts new file mode 100644 index 0000000000000..f38de58375302 --- /dev/null +++ b/packages/@aws-cdk/aws-apigateway/lib/model.ts @@ -0,0 +1,46 @@ +export interface IModel { + readonly modelId: string; +} + +/** + * Represents a reference to a REST API's Empty model, which is available + * as part of the model collection by default. This can be used for mapping + * JSON responses from an integration to what is returned to a client, + * where strong typing is not required. In the absence of any defined + * model, the Empty model will be used to return the response payload + * unmapped. + * + * Definition + * { + * "$schema" : "http://json-schema.org/draft-04/schema#", + * "title" : "Empty Schema", + * "type" : "object" + * } + * + * @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'; +} + +/** + * Represents a reference to a REST API's Error model, which is available + * as part of the model collection by default. This can be used for mapping + * error JSON responses from an integration to a client, where a simple + * generic message field is sufficient to map and return an error payload. + * + * Definition + * { + * "$schema" : "http://json-schema.org/draft-04/schema#", + * "title" : "Error Schema", + * "type" : "object", + * "properties" : { + * "message" : { "type" : "string" } + * } + * } + */ +export class ErrorModel implements IModel { + public readonly modelId = 'Error'; +} + +// TODO: Implement Model, enabling management of custom models. \ No newline at end of file diff --git a/packages/@aws-cdk/aws-apigateway/test/test.method.ts b/packages/@aws-cdk/aws-apigateway/test/test.method.ts index c1e0ee124b1d3..fb87eb75fa81b 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.method.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.method.ts @@ -5,7 +5,7 @@ import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import apigateway = require('../lib'); -import { ConnectionType } from '../lib'; +import { ConnectionType, EmptyModel, ErrorModel } from '../lib'; export = { 'default setup'(test: Test) { @@ -304,6 +304,63 @@ export = { test.done(); }, + 'methodResponse set one or more method responses via options'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const api = new apigateway.RestApi(stack, 'test-api', { deploy: false }); + + // WHEN + new apigateway.Method(stack, 'method-man', { + httpMethod: 'GET', + resource: api.root, + options: { + methodResponses: [{ + statusCode: '200' + }, { + statusCode: "400", + responseParameters: { + 'method.response.header.killerbees': false + } + }, { + statusCode: "500", + responseParameters: { + 'method.response.header.errthing': true + }, + responseModels: { + 'application/json': new EmptyModel(), + 'text/plain': new ErrorModel() + } + } + ] + } + }); + + // THEN + expect(stack).to(haveResource('AWS::ApiGateway::Method', { + HttpMethod: 'GET', + MethodResponses: [{ + StatusCode: "200" + }, { + StatusCode: "400", + ResponseParameters: { + 'method.response.header.killerbees': false + } + }, { + StatusCode: "500", + ResponseParameters: { + 'method.response.header.errthing': true + }, + ResponseModels: { + 'application/json': 'Empty', + 'text/plain': 'Error' + } + } + ] + })); + + test.done(); + }, + 'multiple integration responses can be used'(test: Test) { // @see https://github.com/awslabs/aws-cdk/issues/1608 // GIVEN const stack = new cdk.Stack();