Skip to content

Commit b77e937

Browse files
authored
fix(sqs): does not print all failed validations for Queue props (#33070)
### Issue #33098 Closes #33098. ### Reason for this change When initializing a new SQS Queue, props validation throws an error at the first validation issue encountered. If there are multiple validation issues, the user is only informed of the first one. ### Description of changes Using `validateAllProps` presents all validation errors to the user at once. If `redriveAllowPolicy` is enabled, the policy will also be evaluated in the same way. ### Describe any new or updated permissions being added No permissions changes. ### Description of how you validated changes Adjusted and added unit tests. Ran integration tests. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent c0ed449 commit b77e937

File tree

4 files changed

+137
-35
lines changed

4 files changed

+137
-35
lines changed

packages/aws-cdk-lib/aws-sqs/lib/queue.ts

+3-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Construct } from 'constructs';
22
import { IQueue, QueueAttributes, QueueBase, QueueEncryption } from './queue-base';
33
import { CfnQueue } from './sqs.generated';
4-
import { validateProps } from './validate-props';
4+
import { validateQueueProps, validateRedriveAllowPolicy } from './validate-queue-props';
55
import * as iam from '../../aws-iam';
66
import * as kms from '../../aws-kms';
77
import { Duration, RemovalPolicy, Stack, Token, ArnFormat, Annotations } from '../../core';
@@ -383,20 +383,10 @@ export class Queue extends QueueBase {
383383
physicalName: props.queueName,
384384
});
385385

386-
validateProps(this, props);
386+
validateQueueProps(this, props);
387387

388388
if (props.redriveAllowPolicy) {
389-
const { redrivePermission, sourceQueues } = props.redriveAllowPolicy;
390-
if (redrivePermission === RedrivePermission.BY_QUEUE) {
391-
if (!sourceQueues || sourceQueues.length === 0) {
392-
throw new ValidationError('At least one source queue must be specified when RedrivePermission is set to \'byQueue\'', this);
393-
}
394-
if (sourceQueues && sourceQueues.length > 10) {
395-
throw new ValidationError('Up to 10 sourceQueues can be specified. Set RedrivePermission to \'allowAll\' to specify more', this);
396-
}
397-
} else if (redrivePermission && sourceQueues) {
398-
throw new ValidationError('sourceQueues cannot be configured when RedrivePermission is set to \'allowAll\' or \'denyAll\'', this);
399-
}
389+
validateRedriveAllowPolicy(this, props.redriveAllowPolicy);
400390
}
401391

402392
const redrivePolicy = props.deadLetterQueue

packages/aws-cdk-lib/aws-sqs/lib/validate-props.ts

-20
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Construct } from 'constructs';
2+
import { Queue, QueueProps, RedriveAllowPolicy, RedrivePermission } from './index';
3+
import { Token } from '../../core';
4+
import { validateAllProps, ValidationRule } from '../../core/lib/helpers-internal';
5+
6+
function validateRange(value: number | undefined, minValue: number, maxValue: number): boolean {
7+
return value !== undefined && !Token.isUnresolved(value) && (value < minValue || value > maxValue);
8+
}
9+
10+
const queueValidationRules: ValidationRule<QueueProps>[] = [
11+
{
12+
condition: (props) => validateRange(props.deliveryDelay?.toSeconds(), 0, 900),
13+
message: (props) => `delivery delay must be between 0 and 900 seconds, but ${props.deliveryDelay?.toSeconds()} was provided`,
14+
},
15+
{
16+
condition: (props) => validateRange(props.maxMessageSizeBytes, 1_024, 262_144),
17+
message: (props) => `maximum message size must be between 1,024 and 262,144 bytes, but ${props.maxMessageSizeBytes} was provided`,
18+
},
19+
{
20+
condition: (props) => validateRange(props.retentionPeriod?.toSeconds(), 60, 1_209_600),
21+
message: (props) => `message retention period must be between 60 and 1,209,600 seconds, but ${props.retentionPeriod?.toSeconds()} was provided`,
22+
},
23+
{
24+
condition: (props) => validateRange(props.receiveMessageWaitTime?.toSeconds(), 0, 20),
25+
message: (props) => `receive wait time must be between 0 and 20 seconds, but ${props.receiveMessageWaitTime?.toSeconds()} was provided`,
26+
},
27+
{
28+
condition: (props) => validateRange(props.visibilityTimeout?.toSeconds(), 0, 43_200),
29+
message: (props) => `visibility timeout must be between 0 and 43,200 seconds, but ${props.visibilityTimeout?.toSeconds()} was provided`,
30+
},
31+
{
32+
condition: (props) => validateRange(props.deadLetterQueue?.maxReceiveCount, 1, Number.MAX_SAFE_INTEGER),
33+
message: (props) => `dead letter target maximum receive count must be 1 or more, but ${props.deadLetterQueue?.maxReceiveCount} was provided`,
34+
},
35+
];
36+
37+
const redriveValidationRules: ValidationRule<RedriveAllowPolicy>[] = [
38+
{
39+
condition: ({ redrivePermission, sourceQueues }) =>
40+
redrivePermission === RedrivePermission.BY_QUEUE && (!sourceQueues || sourceQueues.length === 0),
41+
message: () => 'At least one source queue must be specified when RedrivePermission is set to \'byQueue\'',
42+
},
43+
{
44+
condition: ({ redrivePermission, sourceQueues }) =>
45+
!!(redrivePermission === RedrivePermission.BY_QUEUE && sourceQueues && sourceQueues.length > 10),
46+
message: () => 'Up to 10 sourceQueues can be specified. Set RedrivePermission to \'allowAll\' to specify more',
47+
},
48+
{
49+
condition: ({ redrivePermission, sourceQueues }) =>
50+
!!((redrivePermission === RedrivePermission.ALLOW_ALL || redrivePermission === RedrivePermission.DENY_ALL) && sourceQueues),
51+
message: () => 'sourceQueues cannot be configured when RedrivePermission is set to \'allowAll\' or \'denyAll\'',
52+
},
53+
];
54+
55+
export function validateQueueProps(scope: Construct, props: QueueProps) {
56+
validateAllProps(scope, Queue.name, props, queueValidationRules);
57+
}
58+
59+
export function validateRedriveAllowPolicy(scope: Construct, policy: RedriveAllowPolicy) {
60+
validateAllProps(scope, Queue.name, policy, redriveValidationRules);
61+
}

packages/aws-cdk-lib/aws-sqs/test/sqs.test.ts

+73-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as iam from '../../aws-iam';
33
import * as kms from '../../aws-kms';
44
import { CfnParameter, Duration, Stack, App, Token } from '../../core';
55
import * as sqs from '../lib';
6+
import { validateRedriveAllowPolicy } from '../lib/validate-queue-props';
67

78
/* eslint-disable quote-props */
89

@@ -62,18 +63,29 @@ test('with a dead letter queue', () => {
6263
expect(queue.deadLetterQueue).toEqual(dlqProps);
6364
});
6465

66+
test('multiple prop validation errors are presented to the user (out-of-range retentionPeriod and deliveryDelay)', () => {
67+
// GIVEN
68+
const stack = new Stack();
69+
70+
// THEN
71+
expect(() => new sqs.Queue(stack, 'MyQueue', {
72+
retentionPeriod: Duration.seconds(30),
73+
deliveryDelay: Duration.minutes(16),
74+
})).toThrow('Queue initialization failed due to the following validation error(s):\n- delivery delay must be between 0 and 900 seconds, but 960 was provided\n- message retention period must be between 60 and 1,209,600 seconds, but 30 was provided');
75+
});
76+
6577
test('message retention period must be between 1 minute to 14 days', () => {
6678
// GIVEN
6779
const stack = new Stack();
6880

6981
// THEN
7082
expect(() => new sqs.Queue(stack, 'MyQueue', {
7183
retentionPeriod: Duration.seconds(30),
72-
})).toThrow(/message retention period must be 60 seconds or more/);
84+
})).toThrow('Queue initialization failed due to the following validation error(s):\n- message retention period must be between 60 and 1,209,600 seconds, but 30 was provided');
7385

7486
expect(() => new sqs.Queue(stack, 'AnotherQueue', {
7587
retentionPeriod: Duration.days(15),
76-
})).toThrow(/message retention period must be 1209600 seconds or less/);
88+
})).toThrow('Queue initialization failed due to the following validation error(s):\n- message retention period must be between 60 and 1,209,600 seconds, but 1296000 was provided');
7789
});
7890

7991
test('message retention period can be provided as a parameter', () => {
@@ -155,6 +167,65 @@ test('addToPolicy will automatically create a policy for this queue', () => {
155167
});
156168
});
157169

170+
describe('validateRedriveAllowPolicy', () => {
171+
test('does not throw for valid policy', () => {
172+
// GIVEN
173+
const stack = new Stack();
174+
175+
// WHEN
176+
const redriveAllowPolicy = { redrivePermission: sqs.RedrivePermission.ALLOW_ALL };
177+
178+
// THEN
179+
expect(() => validateRedriveAllowPolicy(stack, redriveAllowPolicy)).not.toThrow();
180+
});
181+
182+
test('throws when sourceQueues is provided with ALLOW_ALL permission', () => {
183+
// GIVEN
184+
const stack = new Stack();
185+
186+
// WHEN
187+
const sourceQueue = new sqs.Queue(stack, 'SourceQueue');
188+
const redriveAllowPolicy = {
189+
redrivePermission: sqs.RedrivePermission.ALLOW_ALL,
190+
sourceQueues: [sourceQueue],
191+
};
192+
193+
// THEN
194+
expect(() => validateRedriveAllowPolicy(stack, redriveAllowPolicy))
195+
.toThrow("Queue initialization failed due to the following validation error(s):\n- sourceQueues cannot be configured when RedrivePermission is set to 'allowAll' or 'denyAll'");
196+
});
197+
198+
test('throws when sourceQueues is not provided with BY_QUEUE permission', () => {
199+
// GIVEN
200+
const stack = new Stack();
201+
202+
// WHEN
203+
const redriveAllowPolicy = {
204+
redrivePermission: sqs.RedrivePermission.BY_QUEUE,
205+
};
206+
207+
// THEN
208+
expect(() => validateRedriveAllowPolicy(stack, redriveAllowPolicy))
209+
.toThrow("Queue initialization failed due to the following validation error(s):\n- At least one source queue must be specified when RedrivePermission is set to 'byQueue'");
210+
});
211+
212+
test('throws when more than 10 sourceQueues are provided', () => {
213+
// GIVEN
214+
const stack = new Stack();
215+
216+
// WHEN
217+
const sourceQueues = Array(11).fill(null).map((_, i) => new sqs.Queue(stack, `SourceQueue${i}`));
218+
const redriveAllowPolicy = {
219+
redrivePermission: sqs.RedrivePermission.BY_QUEUE,
220+
sourceQueues,
221+
};
222+
223+
// THEN
224+
expect(() => validateRedriveAllowPolicy(stack, redriveAllowPolicy))
225+
.toThrow("Queue initialization failed due to the following validation error(s):\n- Up to 10 sourceQueues can be specified. Set RedrivePermission to 'allowAll' to specify more");
226+
});
227+
});
228+
158229
describe('export and import', () => {
159230
test('importing works correctly', () => {
160231
// GIVEN

0 commit comments

Comments
 (0)