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(aws-apigateway): expand RestApi support to models, parameters and validators #2960

Merged
merged 17 commits into from
Jun 27, 2019
Merged
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
Prev Previous commit
Next Next commit
feat(aws-apigateway): expand RestApi support to models, parameters an…
…d validators

Fixes #905: "apigateway: "methodResponses" is missing from MethodOptions"
Fixes #1695: apigateway: missing support for models
Fixes #727: API Gateway: improve API for request parameters and responses
Fixes #723: API Gateway: missing features
Fixes #2957: RestApi to use logical id as a name for APIs instrea of name of current construct
julienlepine committed Jun 20, 2019
commit 221fc0dada752f921176f8093239a3b14e8ca735
144 changes: 141 additions & 3 deletions packages/@aws-cdk/aws-apigateway/README.md
Original file line number Diff line number Diff line change
@@ -152,6 +152,147 @@ plan.addApiStage({
});
```

### Working with models

When you work with Lambda integrations that are not Proxy integrations, you
have to define your models and mappings for the request, response, and integration.

```ts
const hello = new lambda.Function(this, 'hello', {
runtime: lambda.Runtime.Nodejs10x,
handler: 'hello.handler',
code: lambda.Code.asset('lambda')
});

const api = new apigateway.RestApi(this, 'hello-api', { });
const resource = api.root.addResource('v1');
```

You can define more parameters on the integration to tune the behavior of API Gateway

```ts
const integration = new LambdaIntegration(hello, {
proxy: false,
requestParameters: {
// You can define mapping parameters from your method to your integration
// - Destination parameters (the key) are the integration parameters (used in mappings)
// - Source parameters (the value) are the source request parameters or expressions
// @see: https://docs.aws.amazon.com/apigateway/latest/developerguide/request-response-data-mappings.html
"integration.request.querystring.who": "method.request.querystring.who"
},
allowTestInvoke: true,
requestTemplates: {
// You can define a mapping that will build a payload for your integration, based
// on the integration parameters that you have specified
// Check: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
"application/json": '{ "action": "sayHello", "pollId": "$util.escapeJavaScript($input.params(\'who\'))" }'
},
// This parameter defines the behavior of the engine is no suitable response template is found
passthroughBehavior: PassthroughBehavior.Never,
integrationResponses: [
{
// Successful response from the Lambda function, no filter defined
// - the selectionPattern filter only tests the error message
// We will set the response status code to 200
statusCode: "200",
responseTemplates: {
// This template takes the "message" result from the Lambda function, adn embeds it in a JSON response
// Check https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
"application/json": '{ "state": "ok", "greeting": "$util.escapeJavaScript($input.body)" }'
},
responseParameters: {
// We can map response parameters
// - Destination parameters (the key) are the response parameters (used in mappings)
// - Source parameters (the value) are the integration response parameters or expressions
'method.response.header.Content-Type': "'application/json'",
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Credentials': "'true'"
}
},
{
// For errors, we check if the error message is not empty, get the error data
selectionPattern: '.+',
// We will set the response status code to 200
statusCode: "400",
responseTemplates: {
"application/json": '{ "state": "error", "message": "$util.escapeJavaScript($input.path(\'$.errorMessage\'))" }'
},
responseParameters: {
'method.response.header.Content-Type': "'application/json'",
'method.response.header.Access-Control-Allow-Origin': "'*'",
'method.response.header.Access-Control-Allow-Credentials': "'true'"
}
}
]
});

```

You can define validation models for your responses (and requests)

```ts
// We define the JSON Schema for the transformed valid response
const responseModel = api.addModel('ResponseModel', {
contentType: "application/json",
name: 'ResponseModel',
schema: { "$schema": "http://json-schema.org/draft-04/schema#", "title": "pollResponse", "type": "object", "properties": { "state": { "type": "string" }, "greeting": { "type": "string" } } }
});

// We define the JSON Schema for the transformed error response
const errorResponseModel = api.addModel('ErrorResponseModel', {
contentType: "application/json",
name: 'ErrorResponseModel',
schema: { "$schema": "http://json-schema.org/draft-04/schema#", "title": "errorResponse", "type": "object", "properties": { "state": { "type": "string" }, "message": { "type": "string" } } }
});

```

And reference all on your method definition.

```ts
// If you want to define parameter mappings for the request, you need a validator
const validator = api.addRequestValidator('DefaultValidator', {
validateRequestBody: false,
validateRequestParameters: true
});
resource.addMethod('GET', integration, {
// We can mark the parameters as required
requestParameters: {
"method.request.querystring.who": true
},
// We need to set the validator for ensuring they are passed
requestValidator: validator,
methodResponses: [
{
// Successful response from the integration
statusCode: "200",
// Define what parameters are allowed or not
responseParameters: {
'method.response.header.Content-Type': true,
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Credentials': true
},
// Validate the schema on the response
responseModels: {
"application/json": responseModel
}
},
{
// Same thing for the error responses
statusCode: "400",
responseParameters: {
'method.response.header.Content-Type': true,
'method.response.header.Access-Control-Allow-Origin': true,
'method.response.header.Access-Control-Allow-Credentials': true
},
responseModels: {
"application/json": errorResponseModel
}
}
]
});
```

#### Default Integration and Method Options

The `defaultIntegration` and `defaultMethodOptions` properties can be used to
@@ -256,12 +397,9 @@ to allow users revert the stage to an old deployment manually.

### Missing Features

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)

----

1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-apigateway/lib/index.ts
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ export * from './usage-plan';
export * from './vpc-link';
export * from './methodresponse';
export * from './model';
export * from './requestvalidator';

// AWS::ApiGateway CloudFormation Resources:
export * from './apigateway.generated';
36 changes: 32 additions & 4 deletions packages/@aws-cdk/aws-apigateway/lib/method.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@ import { CfnMethod, CfnMethodProps } from './apigateway.generated';
import { ConnectionType, Integration } from './integration';
import { MockIntegration } from './integrations/mock';
import { MethodResponse } from './methodresponse';
import { IModelRef } from './model';
import { IRequestValidatorRef } from './requestvalidator';
import { IResource } from './resource';
import { RestApi } from './restapi';
import { validateHttpMethod } from './util';
@@ -56,9 +58,17 @@ export interface MethodOptions {
*/
readonly requestParameters?: { [param: string]: boolean };

// TODO:
// - RequestValidatorId
// - RequestModels
/**
* The resources that are used for the response's content type. Specify request
* models as key-value pairs (string-to-string mapping), with a content type
* as the key and a Model resource name as the value
*/
readonly requestModels?: { [param: string]: IModelRef };

/**
* The ID of the associated request validator.
*/
readonly requestValidator?: IRequestValidatorRef;
julienlepine marked this conversation as resolved.
Show resolved Hide resolved
}

export interface MethodProps {
@@ -120,6 +130,8 @@ export class Method extends Resource {
requestParameters: options.requestParameters,
integration: this.renderIntegration(props.integration),
methodResponses: this.renderMethodResponses(options.methodResponses),
requestModels: this.renderRequestModels(options.requestModels),
requestValidatorId: options.requestValidator ? options.requestValidator.requestValidatorId : undefined
};

const resource = new CfnMethod(this, 'Resource', methodProps);
@@ -230,7 +242,7 @@ export class Method extends Resource {
responseModels = {};
for (const contentType in mr.responseModels) {
if (mr.responseModels.hasOwnProperty(contentType)) {
responseModels[contentType] = mr.responseModels[contentType].modelId;
responseModels[contentType] = mr.responseModels[contentType].modelName;
}
}
}
@@ -244,6 +256,22 @@ export class Method extends Resource {
return methodResponseProp;
});
}

private renderRequestModels(requestModels: { [param: string]: IModelRef } | undefined): { [param: string]: string } | undefined {
if (!requestModels) {
// Fall back to nothing
return undefined;
}

const models: {[param: string]: string} = {};
for (const contentType in requestModels) {
if (requestModels.hasOwnProperty(contentType)) {
models[contentType] = requestModels[contentType].modelName;
}
}

return models;
}
}

export enum AuthorizationType {
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-apigateway/lib/methodresponse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IModel } from './model';
import { IModelRef } from './model';

export interface MethodResponse {

@@ -24,5 +24,5 @@ export interface MethodResponse {
* resource name as the value.
* @default None
*/
readonly responseModels?: { [contentType: string]: IModel };
readonly responseModels?: { [contentType: string]: IModelRef };
}
Loading