Skip to content

Commit 12e6380

Browse files
julienlepineElad Ben-Israel
authored andcommitted
feat(aws-apigateway): expand RestApi support to models, parameters and validators (#2960)
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 instead of name of current construct Adds support for JsonSchema in Model Aligns Model to the PhysicalName convention. No breaking change, documentation updated
1 parent b84caab commit 12e6380

File tree

13 files changed

+926
-41
lines changed

13 files changed

+926
-41
lines changed

packages/@aws-cdk/aws-apigateway/README.md

Lines changed: 141 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,147 @@ plan.addApiStage({
155155
});
156156
```
157157

158+
### Working with models
159+
160+
When you work with Lambda integrations that are not Proxy integrations, you
161+
have to define your models and mappings for the request, response, and integration.
162+
163+
```ts
164+
const hello = new lambda.Function(this, 'hello', {
165+
runtime: lambda.Runtime.Nodejs10x,
166+
handler: 'hello.handler',
167+
code: lambda.Code.asset('lambda')
168+
});
169+
170+
const api = new apigateway.RestApi(this, 'hello-api', { });
171+
const resource = api.root.addResource('v1');
172+
```
173+
174+
You can define more parameters on the integration to tune the behavior of API Gateway
175+
176+
```ts
177+
const integration = new LambdaIntegration(hello, {
178+
proxy: false,
179+
requestParameters: {
180+
// You can define mapping parameters from your method to your integration
181+
// - Destination parameters (the key) are the integration parameters (used in mappings)
182+
// - Source parameters (the value) are the source request parameters or expressions
183+
// @see: https://docs.aws.amazon.com/apigateway/latest/developerguide/request-response-data-mappings.html
184+
"integration.request.querystring.who": "method.request.querystring.who"
185+
},
186+
allowTestInvoke: true,
187+
requestTemplates: {
188+
// You can define a mapping that will build a payload for your integration, based
189+
// on the integration parameters that you have specified
190+
// Check: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
191+
"application/json": '{ "action": "sayHello", "pollId": "$util.escapeJavaScript($input.params(\'who\'))" }'
192+
},
193+
// This parameter defines the behavior of the engine is no suitable response template is found
194+
passthroughBehavior: PassthroughBehavior.Never,
195+
integrationResponses: [
196+
{
197+
// Successful response from the Lambda function, no filter defined
198+
// - the selectionPattern filter only tests the error message
199+
// We will set the response status code to 200
200+
statusCode: "200",
201+
responseTemplates: {
202+
// This template takes the "message" result from the Lambda function, adn embeds it in a JSON response
203+
// Check https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
204+
"application/json": '{ "state": "ok", "greeting": "$util.escapeJavaScript($input.body)" }'
205+
},
206+
responseParameters: {
207+
// We can map response parameters
208+
// - Destination parameters (the key) are the response parameters (used in mappings)
209+
// - Source parameters (the value) are the integration response parameters or expressions
210+
'method.response.header.Content-Type': "'application/json'",
211+
'method.response.header.Access-Control-Allow-Origin': "'*'",
212+
'method.response.header.Access-Control-Allow-Credentials': "'true'"
213+
}
214+
},
215+
{
216+
// For errors, we check if the error message is not empty, get the error data
217+
selectionPattern: '(\n|.)+',
218+
// We will set the response status code to 200
219+
statusCode: "400",
220+
responseTemplates: {
221+
"application/json": '{ "state": "error", "message": "$util.escapeJavaScript($input.path(\'$.errorMessage\'))" }'
222+
},
223+
responseParameters: {
224+
'method.response.header.Content-Type': "'application/json'",
225+
'method.response.header.Access-Control-Allow-Origin': "'*'",
226+
'method.response.header.Access-Control-Allow-Credentials': "'true'"
227+
}
228+
}
229+
]
230+
});
231+
232+
```
233+
234+
You can define validation models for your responses (and requests)
235+
236+
```ts
237+
// We define the JSON Schema for the transformed valid response
238+
const responseModel = api.addModel('ResponseModel', {
239+
contentType: "application/json",
240+
modelName: 'ResponseModel',
241+
schema: { "$schema": "http://json-schema.org/draft-04/schema#", "title": "pollResponse", "type": "object", "properties": { "state": { "type": "string" }, "greeting": { "type": "string" } } }
242+
});
243+
244+
// We define the JSON Schema for the transformed error response
245+
const errorResponseModel = api.addModel('ErrorResponseModel', {
246+
contentType: "application/json",
247+
modelName: 'ErrorResponseModel',
248+
schema: { "$schema": "http://json-schema.org/draft-04/schema#", "title": "errorResponse", "type": "object", "properties": { "state": { "type": "string" }, "message": { "type": "string" } } }
249+
});
250+
251+
```
252+
253+
And reference all on your method definition.
254+
255+
```ts
256+
// If you want to define parameter mappings for the request, you need a validator
257+
const validator = api.addRequestValidator('DefaultValidator', {
258+
validateRequestBody: false,
259+
validateRequestParameters: true
260+
});
261+
resource.addMethod('GET', integration, {
262+
// We can mark the parameters as required
263+
requestParameters: {
264+
"method.request.querystring.who": true
265+
},
266+
// We need to set the validator for ensuring they are passed
267+
requestValidator: validator,
268+
methodResponses: [
269+
{
270+
// Successful response from the integration
271+
statusCode: "200",
272+
// Define what parameters are allowed or not
273+
responseParameters: {
274+
'method.response.header.Content-Type': true,
275+
'method.response.header.Access-Control-Allow-Origin': true,
276+
'method.response.header.Access-Control-Allow-Credentials': true
277+
},
278+
// Validate the schema on the response
279+
responseModels: {
280+
"application/json": responseModel
281+
}
282+
},
283+
{
284+
// Same thing for the error responses
285+
statusCode: "400",
286+
responseParameters: {
287+
'method.response.header.Content-Type': true,
288+
'method.response.header.Access-Control-Allow-Origin': true,
289+
'method.response.header.Access-Control-Allow-Credentials': true
290+
},
291+
responseModels: {
292+
"application/json": errorResponseModel
293+
}
294+
}
295+
]
296+
});
297+
```
298+
158299
#### Default Integration and Method Options
159300

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

260401
### Missing Features
261402

262-
See [awslabs/aws-cdk#723](https://github.com/awslabs/aws-cdk/issues/723) for a
263-
list of missing features.
264403

265404
### Roadmap
266405

267-
- [ ] Support defining REST API Models [#1695](https://github.com/awslabs/aws-cdk/issues/1695)
268406

269407
----
270408

packages/@aws-cdk/aws-apigateway/lib/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export * from './usage-plan';
1111
export * from './vpc-link';
1212
export * from './methodresponse';
1313
export * from './model';
14+
export * from './requestvalidator';
1415
export * from './authorizer';
16+
export * from './json-schema';
1517

1618
// AWS::ApiGateway CloudFormation Resources:
1719
export * from './apigateway.generated';
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
export enum JsonSchemaVersion {
2+
/**
3+
* In API Gateway models are defined using the JSON schema draft 4.
4+
* @see https://tools.ietf.org/html/draft-zyp-json-schema-04
5+
*/
6+
DRAFT4 = 'http://json-schema.org/draft-04/schema#',
7+
DRAFT7 = 'http://json-schema.org/draft-07/schema#'
8+
}
9+
10+
export enum JsonSchemaType {
11+
NULL = "null",
12+
BOOLEAN = "boolean",
13+
OBJECT = "object",
14+
ARRAY = "array",
15+
NUMBER = "number",
16+
INTEGER = "integer",
17+
STRING = "string"
18+
}
19+
20+
/**
21+
* Represents a JSON schema definition of the structure of a
22+
* REST API model. Copied from npm module jsonschema.
23+
*
24+
* @see http://json-schema.org/
25+
* @see https://github.com/tdegrunt/jsonschema
26+
*/
27+
export interface JsonSchema {
28+
// Special keywords
29+
readonly schema?: JsonSchemaVersion;
30+
readonly id?: string;
31+
readonly ref?: string;
32+
33+
// Common properties
34+
readonly type?: JsonSchemaType | JsonSchemaType[];
35+
readonly title?: string;
36+
readonly description?: string;
37+
readonly 'enum'?: any[];
38+
readonly format?: string;
39+
readonly definitions?: { [name: string]: JsonSchema };
40+
41+
// Number or Integer
42+
readonly multipleOf?: number;
43+
readonly maximum?: number;
44+
readonly exclusiveMaximum?: boolean;
45+
readonly minimum?: number;
46+
readonly exclusiveMinimum?: boolean;
47+
48+
// String
49+
readonly maxLength?: number;
50+
readonly minLength?: number;
51+
readonly pattern?: string;
52+
53+
// Array
54+
readonly items?: JsonSchema | JsonSchema[];
55+
readonly additionalItems?: JsonSchema[];
56+
readonly maxItems?: number;
57+
readonly minItems?: number;
58+
readonly uniqueItems?: boolean;
59+
readonly contains?: JsonSchema | JsonSchema[];
60+
61+
// Object
62+
readonly maxProperties?: number;
63+
readonly minProperties?: number;
64+
readonly required?: string[];
65+
readonly properties?: { [name: string]: JsonSchema };
66+
readonly additionalProperties?: JsonSchema;
67+
readonly patternProperties?: { [name: string]: JsonSchema };
68+
readonly dependencies?: { [name: string]: JsonSchema | string[] };
69+
readonly propertyNames?: JsonSchema;
70+
71+
// Conditional
72+
readonly allOf?: JsonSchema[];
73+
readonly anyOf?: JsonSchema[];
74+
readonly oneOf?: JsonSchema[];
75+
readonly not?: JsonSchema;
76+
}

packages/@aws-cdk/aws-apigateway/lib/method.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { IAuthorizer } from './authorizer';
44
import { ConnectionType, Integration } from './integration';
55
import { MockIntegration } from './integrations/mock';
66
import { MethodResponse } from './methodresponse';
7+
import { IModel } from './model';
8+
import { IRequestValidator } from './requestvalidator';
79
import { IResource } from './resource';
810
import { RestApi } from './restapi';
911
import { validateHttpMethod } from './util';
@@ -54,9 +56,17 @@ export interface MethodOptions {
5456
*/
5557
readonly requestParameters?: { [param: string]: boolean };
5658

57-
// TODO:
58-
// - RequestValidatorId
59-
// - RequestModels
59+
/**
60+
* The resources that are used for the response's content type. Specify request
61+
* models as key-value pairs (string-to-string mapping), with a content type
62+
* as the key and a Model resource name as the value
63+
*/
64+
readonly requestModels?: { [param: string]: IModel };
65+
66+
/**
67+
* The ID of the associated request validator.
68+
*/
69+
readonly requestValidator?: IRequestValidator;
6070
}
6171

6272
export interface MethodProps {
@@ -119,6 +129,8 @@ export class Method extends Resource {
119129
requestParameters: options.requestParameters,
120130
integration: this.renderIntegration(props.integration),
121131
methodResponses: this.renderMethodResponses(options.methodResponses),
132+
requestModels: this.renderRequestModels(options.requestModels),
133+
requestValidatorId: options.requestValidator ? options.requestValidator.requestValidatorId : undefined
122134
};
123135

124136
const resource = new CfnMethod(this, 'Resource', methodProps);
@@ -243,6 +255,22 @@ export class Method extends Resource {
243255
return methodResponseProp;
244256
});
245257
}
258+
259+
private renderRequestModels(requestModels: { [param: string]: IModel } | undefined): { [param: string]: string } | undefined {
260+
if (!requestModels) {
261+
// Fall back to nothing
262+
return undefined;
263+
}
264+
265+
const models: {[param: string]: string} = {};
266+
for (const contentType in requestModels) {
267+
if (requestModels.hasOwnProperty(contentType)) {
268+
models[contentType] = requestModels[contentType].modelId;
269+
}
270+
}
271+
272+
return models;
273+
}
246274
}
247275

248276
export enum AuthorizationType {

0 commit comments

Comments
 (0)