Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(apigateway): add methodresponse support #1572

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a "@default" clause and describe the behavior if this is not defined

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I can add that. I'll also look at doing the same for the properties of the MethodResponse type itself.

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add docs and provide a link to official documentation describing the keywords 'Empty' and 'Error'.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added some documentation around both the Empty and Error models. As far as reference links, I was able to find some mention in the official docs for the Empty model, but not specifically the Error model (outside of mentioning that Empty and Error are available by default). Are you aware of a page in the documentation that addresses the Error model in any further detail?

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update README roadmap section

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm adding an issue to track the lack of support for API gateway models, and will quote that in the api-gateway readme.

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: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering if a bunch of static properties on Model to make it “enum-like” would make a nicer and more discoverable API here: “Model.Empty” and “Model.Error”

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explored adding static properties to a stubbed Model class, but I get an error that the build wants me to have Model implement cdk.Construct, so I backed away for now. Perhaps there's a way to get the build to temporarily ignore that requirement?

'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