Skip to content

Commit

Permalink
feat(sqs): throw ValidationError instead of untyped errors
Browse files Browse the repository at this point in the history
  • Loading branch information
kaizencc authored and mrgrain committed Jan 22, 2025
1 parent 0b2db62 commit e4e1482
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 25 deletions.
9 changes: 7 additions & 2 deletions packages/aws-cdk-lib/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,14 @@ baseConfig.rules['import/no-extraneous-dependencies'] = [


// no-throw-default-error
const modules = ['aws-s3', 'aws-lambda', 'aws-rds'];
const enableNoThrowDefaultErrorIn = [
'aws-lambda',
'aws-rds',
'aws-s3',
'aws-sqs',
];
baseConfig.overrides.push({
files: modules.map(m => `./${m}/lib/**`),
files: enableNoThrowDefaultErrorIn.map(m => `./${m}/lib/**`),
rules: { "@cdklabs/no-throw-default-error": ['error'] },
});

Expand Down
3 changes: 2 additions & 1 deletion packages/aws-cdk-lib/aws-sqs/lib/policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IQueue } from './queue-base';
import { CfnQueuePolicy } from './sqs.generated';
import { PolicyDocument } from '../../aws-iam';
import { Resource } from '../../core';
import { ValidationError } from '../../core/lib/errors';

/**
* Properties to associate SQS queues with a policy
Expand Down Expand Up @@ -51,6 +52,6 @@ export class QueuePolicy extends Resource {
* @attribute
*/
public get queuePolicyId(): string {
throw new Error('QueuePolicy.queuePolicyId has been removed from CloudFormation');
throw new ValidationError('QueuePolicy.queuePolicyId has been removed from CloudFormation', this);
}
}
25 changes: 13 additions & 12 deletions packages/aws-cdk-lib/aws-sqs/lib/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { validateProps } from './validate-props';
import * as iam from '../../aws-iam';
import * as kms from '../../aws-kms';
import { Duration, RemovalPolicy, Stack, Token, ArnFormat, Annotations } from '../../core';
import { ValidationError } from '../../core/lib/errors';

/**
* Properties for creating a new Queue
Expand Down Expand Up @@ -325,10 +326,10 @@ export class Queue extends QueueBase {
} else {
if (typeof attrs.fifo !== 'undefined') {
if (attrs.fifo && !queueName.endsWith('.fifo')) {
throw new Error("FIFO queue names must end in '.fifo'");
throw new ValidationError("FIFO queue names must end in '.fifo'", this);
}
if (!attrs.fifo && queueName.endsWith('.fifo')) {
throw new Error("Non-FIFO queue name may not end in '.fifo'");
throw new ValidationError("Non-FIFO queue name may not end in '.fifo'", this);
}
}
return queueName.endsWith('.fifo') ? true : false;
Expand Down Expand Up @@ -389,13 +390,13 @@ export class Queue extends QueueBase {
const { redrivePermission, sourceQueues } = props.redriveAllowPolicy;
if (redrivePermission === RedrivePermission.BY_QUEUE) {
if (!sourceQueues || sourceQueues.length === 0) {
throw new Error('At least one source queue must be specified when RedrivePermission is set to \'byQueue\'');
throw new ValidationError('At least one source queue must be specified when RedrivePermission is set to \'byQueue\'', this);
}
if (sourceQueues && sourceQueues.length > 10) {
throw new Error('Up to 10 sourceQueues can be specified. Set RedrivePermission to \'allowAll\' to specify more');
throw new ValidationError('Up to 10 sourceQueues can be specified. Set RedrivePermission to \'allowAll\' to specify more', this);
}
} else if (redrivePermission && sourceQueues) {
throw new Error('sourceQueues cannot be configured when RedrivePermission is set to \'allowAll\' or \'denyAll\'');
throw new ValidationError('sourceQueues cannot be configured when RedrivePermission is set to \'allowAll\' or \'denyAll\'', this);
}
}

Expand Down Expand Up @@ -452,7 +453,7 @@ export class Queue extends QueueBase {
let encryption = props.encryption;

if (encryption === QueueEncryption.SQS_MANAGED && props.encryptionMasterKey) {
throw new Error("'encryptionMasterKey' is not supported if encryption type 'SQS_MANAGED' is used");
throw new ValidationError("'encryptionMasterKey' is not supported if encryption type 'SQS_MANAGED' is used", this);
}

if (encryption !== QueueEncryption.KMS && props.encryptionMasterKey) {
Expand Down Expand Up @@ -513,7 +514,7 @@ export class Queue extends QueueBase {
};
}

throw new Error(`Unexpected 'encryptionType': ${encryption}`);
throw new ValidationError(`Unexpected 'encryptionType': ${encryption}`, this);
}

// Enforce encryption of data in transit
Expand All @@ -537,23 +538,23 @@ export class Queue extends QueueBase {
// If we have a name, see that it agrees with the FIFO setting
if (typeof queueName === 'string') {
if (fifoQueue && !queueName.endsWith('.fifo')) {
throw new Error("FIFO queue names must end in '.fifo'");
throw new ValidationError("FIFO queue names must end in '.fifo'", this);
}
if (!fifoQueue && queueName.endsWith('.fifo')) {
throw new Error("Non-FIFO queue name may not end in '.fifo'");
throw new ValidationError("Non-FIFO queue name may not end in '.fifo'", this);
}
}

if (props.contentBasedDeduplication && !fifoQueue) {
throw new Error('Content-based deduplication can only be defined for FIFO queues');
throw new ValidationError('Content-based deduplication can only be defined for FIFO queues', this);
}

if (props.deduplicationScope && !fifoQueue) {
throw new Error('Deduplication scope can only be defined for FIFO queues');
throw new ValidationError('Deduplication scope can only be defined for FIFO queues', this);
}

if (props.fifoThroughputLimit && !fifoQueue) {
throw new Error('FIFO throughput limit can only be defined for FIFO queues');
throw new ValidationError('FIFO throughput limit can only be defined for FIFO queues', this);
}

return {
Expand Down
22 changes: 12 additions & 10 deletions packages/aws-cdk-lib/aws-sqs/lib/validate-props.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { Construct } from 'constructs';
import { QueueProps } from './index';
import { Token } from '../../core';
import { ValidationError } from '../../core/lib/errors';

export function validateProps(props: QueueProps) {
validateRange('delivery delay', props.deliveryDelay && props.deliveryDelay.toSeconds(), 0, 900, 'seconds');
validateRange('maximum message size', props.maxMessageSizeBytes, 1_024, 262_144, 'bytes');
validateRange('message retention period', props.retentionPeriod && props.retentionPeriod.toSeconds(), 60, 1_209_600, 'seconds');
validateRange('receive wait time', props.receiveMessageWaitTime && props.receiveMessageWaitTime.toSeconds(), 0, 20, 'seconds');
validateRange('visibility timeout', props.visibilityTimeout && props.visibilityTimeout.toSeconds(), 0, 43_200, 'seconds');
validateRange('dead letter target maximum receive count', props.deadLetterQueue && props.deadLetterQueue.maxReceiveCount, 1, +Infinity);
export function validateProps(scope: Construct, props: QueueProps) {
validateRange(scope, 'delivery delay', props.deliveryDelay && props.deliveryDelay.toSeconds(), 0, 900, 'seconds');
validateRange(scope, 'maximum message size', props.maxMessageSizeBytes, 1_024, 262_144, 'bytes');
validateRange(scope, 'message retention period', props.retentionPeriod && props.retentionPeriod.toSeconds(), 60, 1_209_600, 'seconds');
validateRange(scope, 'receive wait time', props.receiveMessageWaitTime && props.receiveMessageWaitTime.toSeconds(), 0, 20, 'seconds');
validateRange(scope, 'visibility timeout', props.visibilityTimeout && props.visibilityTimeout.toSeconds(), 0, 43_200, 'seconds');
validateRange(scope, 'dead letter target maximum receive count', props.deadLetterQueue && props.deadLetterQueue.maxReceiveCount, 1, +Infinity);
}

function validateRange(label: string, value: number | undefined, minValue: number, maxValue: number, unit?: string) {
function validateRange(scope: Construct, label: string, value: number | undefined, minValue: number, maxValue: number, unit?: string) {
if (value === undefined || Token.isUnresolved(value)) { return; }
const unitSuffix = unit ? ` ${unit}` : '';
if (value < minValue) { throw new Error(`${label} must be ${minValue}${unitSuffix} or more, but ${value} was provided`); }
if (value > maxValue) { throw new Error(`${label} must be ${maxValue}${unitSuffix} or less, but ${value} was provided`); }
if (value < minValue) { throw new ValidationError(`${label} must be ${minValue}${unitSuffix} or more, but ${value} was provided`, scope); }
if (value > maxValue) { throw new ValidationError(`${label} must be ${maxValue}${unitSuffix} or less, but ${value} was provided`, scope); }
}

0 comments on commit e4e1482

Please sign in to comment.