Skip to content

Commit

Permalink
Merge pull request #1218 from awslabs/bump/2.72.0
Browse files Browse the repository at this point in the history
chore(release): 2.72.0
  • Loading branch information
biffgaut authored Oct 6, 2024
2 parents 88a0530 + ba11444 commit 8e5d552
Show file tree
Hide file tree
Showing 115 changed files with 3,393 additions and 710 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [2.72.0](https://github.com/awslabs/aws-solutions-constructs/compare/v2.71.0...v2.72.0) (2024-10-06)

Build on CDK v2.161.0

### Features

* **aws-apigateway-sqs:** add schema validation for create operation ([#1216](https://github.com/awslabs/aws-solutions-constructs/issues/1216)) ([9d42398](https://github.com/awslabs/aws-solutions-constructs/commit/9d423986b8aaee4c921000e8738ce8ad5ef78765))

## [2.71.0](https://github.com/awslabs/aws-solutions-constructs/compare/v2.70.0...v2.71.0) (2024-09-27)

Build on CDK v2.160.0
Expand Down
2 changes: 1 addition & 1 deletion deployment/v2/align-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const nullVersionMarker = process.argv[2];
const targetSolutionsConstructsVersion = process.argv[3];

// these versions need to be sourced from a config file
const awsCdkLibVersion = '2.160.0';
const awsCdkLibVersion = '2.161.0';

for (const file of process.argv.splice(4)) {
const pkg = JSON.parse(fs.readFileSync(file).toString());
Expand Down
2 changes: 1 addition & 1 deletion source/lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"patterns/@aws-solutions-constructs/*"
],
"rejectCycles": "true",
"version": "2.71.0"
"version": "2.72.0"
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ new ApiGatewayToSqs(this, "ApiGatewayToSqsPattern", new ApiGatewayToSqsProps.Bui
|enableEncryptionWithCustomerManagedKey?|`boolean`|If no key is provided, this flag determines whether the queue is encrypted with a new CMK or an AWS managed key. This flag is ignored if any of the following are defined: queueProps.encryptionMasterKey, encryptionKey or encryptionKeyProps.|
|encryptionKey?|[`kms.Key`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_kms.Key.html)|An optional, imported encryption key to encrypt the SQS Queue with.|
|encryptionKeyProps?|[`kms.KeyProps`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_kms.Key.html#construct-props)|Optional user provided properties to override the default properties for the KMS encryption key used to encrypt the SQS queue with.|
|messageSchema?|{ [contentType: string]: [api.JsonSchema](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_apigateway.JsonSchema.html); }|Optional schema to define format of incoming message in API request body. Example: { "application/json": { schema: api.JsonSchemaVersion.DRAFT4, title: 'pollResponse', type: api.JsonSchemaType.OBJECT, required: ['firstProperty', 'antotherProperty'], additionalProperties: false, properties: { firstProperty: { type: api.JsonSchemaType.STRING }, antotherProperty: { type: api.JsonSchemaType.STRING } } } Only relevant for create operation, if allowCreateOperation is not true, then supplying this is an error. Sending this value causes this construct to turn on validation for the request body. @default - None|

## Pattern Properties

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,29 @@ export interface ApiGatewayToSqsProps {
* @default - None
*/
readonly encryptionKeyProps?: kms.KeyProps;
/**
* Optional schema to define format of incoming message in API request body. Example:
* {
* "application/json": {
* schema: api.JsonSchemaVersion.DRAFT4,
* title: 'pollResponse',
* type: api.JsonSchemaType.OBJECT,
* required: ['firstProperty', 'antotherProperty'],
* additionalProperties: false,
* properties: {
* firstProperty: { type: api.JsonSchemaType.STRING },
* antotherProperty: { type: api.JsonSchemaType.STRING }
* }
* }
*
* Only relevant for create operation, if allowCreateOperation is not true, then supplying this
* is an error.
*
* Sending this value causes this construct to turn on validation for the request body.
*
* @default - None
*/
readonly messageSchema?: { [contentType: string]: api.JsonSchema; };
}

/**
Expand Down Expand Up @@ -257,15 +280,15 @@ export class ApiGatewayToSqs extends Construct {

if (this.CheckCreateRequestProps(props)) {
throw new Error(`The 'allowCreateOperation' property must be set to true when setting any of the following: ` +
`'createRequestTemplate', 'additionalCreateRequestTemplates', 'createIntegrationResponses'`);
`'createRequestTemplate', 'additionalCreateRequestTemplates', 'createIntegrationResponses', 'messageSchema'`);
}
if (this.CheckReadRequestProps(props)) {
throw new Error(`The 'allowReadOperation' property must be set to true or undefined when setting any of the following: ` +
`'readRequestTemplate', 'additionalReadRequestTemplates', 'readIntegrationResponses'`);
}
if (this.CheckDeleteRequestProps(props)) {
throw new Error(`The 'allowDeleteOperation' property must be set to true when setting any of the following: ` +
`'deleteRequestTemplate', 'additionalDeleteRequestTemplates', 'deleteIntegrationResponses'`);
`'deleteRequestTemplate', 'additionalDeleteRequestTemplates', 'deleteIntegrationResponses'`);
}

// Setup the queue
Expand Down Expand Up @@ -296,7 +319,35 @@ export class ApiGatewayToSqs extends Construct {
// Create
const createRequestTemplate = props.createRequestTemplate ?? this.defaultCreateRequestTemplate;
if (props.allowCreateOperation && props.allowCreateOperation === true) {
const createMethodOptions: api.MethodOptions = props.createMethodResponses ? { methodResponses: props.createMethodResponses } : {};
let createMethodOptions: api.MethodOptions = {};

// If the client supplied model definitions, set requestModels
if (props.messageSchema) {
const requestModels: { [contentType: string]: api.IModel; } = {};
Object.keys(props.messageSchema).forEach(key => {
const contentType = key;
const schema = props.messageSchema![key];

const newModel = new api.Model(this, `${id}-model-${defaults.removeNonAlphanumeric(contentType)}`, {
restApi: this.apiGateway,
contentType,
schema
});

requestModels[contentType] = newModel;
createMethodOptions = defaults.overrideProps(createMethodOptions, {
requestModels,
requestValidatorOptions: {
validateRequestBody: true
}
});
});
}

if (props.createMethodResponses) {
createMethodOptions = defaults.overrideProps(createMethodOptions, { methodResponses: props.createMethodResponses });
}

this.addActionToPolicy("sqs:SendMessage");
defaults.addProxyMethodToApiResource({
service: "sqs",
Expand Down Expand Up @@ -353,22 +404,22 @@ export class ApiGatewayToSqs extends Construct {
}
private CheckReadRequestProps(props: ApiGatewayToSqsProps): boolean {
if ((props.readRequestTemplate || props.additionalReadRequestTemplates || props.readIntegrationResponses)
&& props.allowReadOperation === false) {
&& props.allowReadOperation === false) {
return true;
}
return false;
}
private CheckDeleteRequestProps(props: ApiGatewayToSqsProps): boolean {
if ((props.deleteRequestTemplate || props.additionalDeleteRequestTemplates || props.deleteIntegrationResponses)
&& props.allowDeleteOperation !== true) {
&& props.allowDeleteOperation !== true) {
return true;
}
return false;
}

private CheckCreateRequestProps(props: ApiGatewayToSqsProps): boolean {
if ((props.createRequestTemplate || props.additionalCreateRequestTemplates || props.createIntegrationResponses)
&& props.allowCreateOperation !== true) {
if ((props.createRequestTemplate || props.additionalCreateRequestTemplates || props.createIntegrationResponses || props.messageSchema)
&& props.allowCreateOperation !== true) {
return true;
}
return false;
Expand All @@ -379,7 +430,7 @@ export class ApiGatewayToSqs extends Construct {
resources: [
this.sqsQueue.queueArn
],
actions: [ `${action}` ]
actions: [`${action}`]
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@

// Imports
import { RemovalPolicy, Stack } from "aws-cdk-lib";
import { ApiGatewayToSqs } from '../lib';
import { ApiGatewayToSqs, ApiGatewayToSqsProps } from '../lib';
import * as api from "aws-cdk-lib/aws-apigateway";
import * as kms from 'aws-cdk-lib/aws-kms';
import * as sqs from "aws-cdk-lib/aws-sqs";
import { Template } from "aws-cdk-lib/assertions";
import { Match, Template } from "aws-cdk-lib/assertions";

test('Test deployment w/o DLQ', () => {
const stack = new Stack();
Expand Down Expand Up @@ -69,7 +69,7 @@ test('Test properties', () => {
deployDeadLetterQueue: true,
maxReceiveCount: 3
});
// Assertion 1
// Assertion 1
expect(pattern.apiGateway).toBeDefined();
// Assertion 2
expect(pattern.sqsQueue).toBeDefined();
Expand Down Expand Up @@ -113,7 +113,7 @@ test('Test deployment for override ApiGateway createRequestTemplate', () => {
const stack = new Stack();

new ApiGatewayToSqs(stack, 'api-gateway-sqs', {
createRequestTemplate: "Action=SendMessage&MessageBody=$util.urlEncode(\"HelloWorld\")",
createRequestTemplate: "Action=SendMessage&MessageBody=$util.urlEncode(\"HelloWorld\")",
allowCreateOperation: true
});
const template = Template.fromStack(stack);
Expand All @@ -131,7 +131,7 @@ test('Test deployment for override ApiGateway getRequestTemplate', () => {
const stack = new Stack();

new ApiGatewayToSqs(stack, 'api-gateway-sqs', {
readRequestTemplate: "Action=HelloWorld",
readRequestTemplate: "Action=HelloWorld",
allowReadOperation: true
});
const template = Template.fromStack(stack);
Expand All @@ -149,7 +149,7 @@ test('Test deployment for override ApiGateway deleteRequestTemplate', () => {
const stack = new Stack();

new ApiGatewayToSqs(stack, 'api-gateway-sqs', {
deleteRequestTemplate: "Action=HelloWorld",
deleteRequestTemplate: "Action=HelloWorld",
allowDeleteOperation: true
});
const template = Template.fromStack(stack);
Expand Down Expand Up @@ -577,13 +577,22 @@ test('Construct uses custom deleteIntegrationResponses property', () => {
});
});

test('Construct throws error when messageSchema is set and allowCreateOperation is not true', () => {
const stack = new Stack();
const app = () => new ApiGatewayToSqs(stack, 'api-gateway-sqs', {
messageSchema: '{}' as any,
});

expect(app).toThrowError(/The 'allowCreateOperation' property must be set to true when setting any of the following: 'createRequestTemplate', 'additionalCreateRequestTemplates', 'createIntegrationResponses', 'messageSchema'/);
});

test('Construct throws error when createRequestTemplate is set and allowCreateOperation is not true', () => {
const stack = new Stack();
const app = () => new ApiGatewayToSqs(stack, 'api-gateway-sqs', {
createRequestTemplate: '{}',
});

expect(app).toThrowError(/The 'allowCreateOperation' property must be set to true when setting any of the following: 'createRequestTemplate', 'additionalCreateRequestTemplates', 'createIntegrationResponses'/);
expect(app).toThrowError(/The 'allowCreateOperation' property must be set to true when setting any of the following: 'createRequestTemplate', 'additionalCreateRequestTemplates', 'createIntegrationResponses', 'messageSchema'/);
});

test('Construct throws error when additionalCreateRequestTemplates is set and allowCreateOperation is not true', () => {
Expand All @@ -592,7 +601,7 @@ test('Construct throws error when additionalCreateRequestTemplates is set and al
additionalCreateRequestTemplates: {}
});

expect(app).toThrowError(/The 'allowCreateOperation' property must be set to true when setting any of the following: 'createRequestTemplate', 'additionalCreateRequestTemplates', 'createIntegrationResponses'/);
expect(app).toThrowError(/The 'allowCreateOperation' property must be set to true when setting any of the following: 'createRequestTemplate', 'additionalCreateRequestTemplates', 'createIntegrationResponses', 'messageSchema'/);
});

test('Construct throws error when createIntegrationResponses is set and allowCreateOperation is not true', () => {
Expand All @@ -601,7 +610,7 @@ test('Construct throws error when createIntegrationResponses is set and allowCre
createIntegrationResponses: []
});

expect(app).toThrowError(/The 'allowCreateOperation' property must be set to true when setting any of the following: 'createRequestTemplate', 'additionalCreateRequestTemplates', 'createIntegrationResponses'/);
expect(app).toThrowError(/The 'allowCreateOperation' property must be set to true when setting any of the following: 'createRequestTemplate', 'additionalCreateRequestTemplates', 'createIntegrationResponses', 'messageSchema'/);
});

test('Construct throws error when readRequestTemplate is set and allowReadOperation is false', () => {
Expand Down Expand Up @@ -694,6 +703,7 @@ test('Construct uses custom createMethodResponses property', () => {
});

const template = Template.fromStack(stack);

template.hasResourceProperties('AWS::ApiGateway::Method', {
HttpMethod: 'POST',
MethodResponses: [{
Expand All @@ -712,7 +722,7 @@ test('Construct uses custom createMethodResponses property', () => {
});
});

test.only('Construct uses custom deleteMethodResponses property', () => {
test('Construct uses custom deleteMethodResponses property', () => {
const stack = new Stack();
new ApiGatewayToSqs(stack, 'api-gateway-sqs', {
allowCreateOperation: false,
Expand Down Expand Up @@ -750,7 +760,7 @@ test.only('Construct uses custom deleteMethodResponses property', () => {
});
});

test.only('Construct uses custom readMethodResponses property', () => {
test('Construct uses custom readMethodResponses property', () => {
const stack = new Stack();
new ApiGatewayToSqs(stack, 'api-gateway-sqs', {
allowCreateOperation: false,
Expand Down Expand Up @@ -787,3 +797,90 @@ test.only('Construct uses custom readMethodResponses property', () => {
}
});
});

test('Construct uses mulitple custom messageSchema', () => {
const stack = new Stack();
const props: ApiGatewayToSqsProps = {
allowCreateOperation: true,
messageSchema: {
"application/json": {
schema: api.JsonSchemaVersion.DRAFT4,
title: 'pollResponse',
type: api.JsonSchemaType.OBJECT,
required: ['state', 'greeting'],
additionalProperties: false,
properties: {
state: { type: api.JsonSchemaType.STRING },
greeting: { type: api.JsonSchemaType.STRING }
}
},
"application/text": {
schema: api.JsonSchemaVersion.DRAFT4,
title: 'pollResponse',
type: api.JsonSchemaType.OBJECT,
additionalProperties: false,
properties: {
textstate: { type: api.JsonSchemaType.STRING },
textgreeting: { type: api.JsonSchemaType.STRING }
}
}
}
};

new ApiGatewayToSqs(stack, 'test-api-gateway-sqs', props);
const template = Template.fromStack(stack);
template.resourceCountIs("AWS::ApiGateway::Model", 2);
template.hasResourceProperties("AWS::ApiGateway::Model", {
ContentType: "application/json",
RestApiId: Match.anyValue(),
Schema: {
$schema: "http://json-schema.org/draft-04/schema#",
title: "pollResponse",
type: "object",
required: [
"state",
"greeting"
],
additionalProperties: false,
properties: {
state: {
type: "string"
},
greeting: {
type: "string"
}
}
}
});
template.hasResourceProperties("AWS::ApiGateway::Model", {
ContentType: "application/text",
RestApiId: Match.anyValue(),
Schema: {
$schema: "http://json-schema.org/draft-04/schema#",
title: "pollResponse",
type: "object",
additionalProperties: false,
properties: {
textstate: {
type: "string"
},
textgreeting: {
type: "string"
}
}
}
});
template.hasResourceProperties("AWS::ApiGateway::Method", {
Integration: {
IntegrationHttpMethod: "POST"
},
RequestModels: {
'application/json': {
Ref: "testapigatewaysqstestapigatewaysqsmodelapplicationjson94EAC0E9"
},
'application/text': {
Ref: "testapigatewaysqstestapigatewaysqsmodelapplicationtext6A72CC47"
}
}
});
});
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"version": "36.0.0",
"version": "38.0.1",
"files": {
"ffcb28580155609b6742f44a39e21a4bc1428993a1c8d27ff75ccd916dc7fc83": {
"a833b41dd73a597b3f3639956815c07229fc3e8ffc7c95190083a2f2150d1741": {
"source": {
"path": "apisqs-custom-method-responses.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "ffcb28580155609b6742f44a39e21a4bc1428993a1c8d27ff75ccd916dc7fc83.json",
"objectKey": "a833b41dd73a597b3f3639956815c07229fc3e8ffc7c95190083a2f2150d1741.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Loading

0 comments on commit 8e5d552

Please sign in to comment.