Skip to content

Commit

Permalink
feat(apigateway): add support for MethodResponse to aws-apigateway. (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
john-shaskin authored and Sam Goodwin committed Feb 13, 2019
1 parent 63ae0b4 commit 46236d9
Show file tree
Hide file tree
Showing 6 changed files with 190 additions and 5 deletions.
4 changes: 4 additions & 0 deletions packages/@aws-cdk/aws-apigateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
56 changes: 52 additions & 4 deletions packages/@aws-cdk/aws-apigateway/lib/method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand Down
28 changes: 28 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/methodresponse.ts
Original file line number Diff line number Diff line change
@@ -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 };
}
46 changes: 46 additions & 0 deletions packages/@aws-cdk/aws-apigateway/lib/model.ts
Original file line number Diff line number Diff line change
@@ -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.
59 changes: 58 additions & 1 deletion packages/@aws-cdk/aws-apigateway/test/test.method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit 46236d9

Please sign in to comment.