From dc6a1e0710a02e4d9b6e73674373ac32d26fc883 Mon Sep 17 00:00:00 2001 From: Momo Kornher Date: Mon, 12 May 2025 11:46:49 +0100 Subject: [PATCH] feat(scheduler): throw typed errors --- packages/aws-cdk-lib/.eslintrc.js | 2 -- .../lib/event-bridge-put-events.ts | 3 ++- .../lib/kinesis-stream-put-record.ts | 4 ++-- .../lib/sage-maker-start-pipeline-execution.ts | 3 ++- .../aws-scheduler-targets/lib/sqs-send-message.ts | 10 +++++----- .../aws-cdk-lib/aws-scheduler-targets/lib/target.ts | 6 +++--- .../aws-scheduler-targets/lib/universal.ts | 8 ++++---- .../aws-scheduler/lib/schedule-expression.ts | 4 ++-- packages/aws-cdk-lib/aws-scheduler/lib/schedule.ts | 12 ++++++------ 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/aws-cdk-lib/.eslintrc.js b/packages/aws-cdk-lib/.eslintrc.js index 9445191d72613..0bf6a15d54af6 100644 --- a/packages/aws-cdk-lib/.eslintrc.js +++ b/packages/aws-cdk-lib/.eslintrc.js @@ -26,8 +26,6 @@ const noThrowDefaultErrorNotYetSupported = [ 'aws-lambda-destinations', 'aws-lambda-event-sources', 'aws-lambda-nodejs', - 'aws-scheduler-targets', - 'aws-scheduler', 'aws-secretsmanager', 'aws-servicecatalog', 'aws-sns-subscriptions', diff --git a/packages/aws-cdk-lib/aws-scheduler-targets/lib/event-bridge-put-events.ts b/packages/aws-cdk-lib/aws-scheduler-targets/lib/event-bridge-put-events.ts index 290124a8588f4..add58b775b3bb 100644 --- a/packages/aws-cdk-lib/aws-scheduler-targets/lib/event-bridge-put-events.ts +++ b/packages/aws-cdk-lib/aws-scheduler-targets/lib/event-bridge-put-events.ts @@ -2,6 +2,7 @@ import { ScheduleTargetBase, ScheduleTargetBaseProps } from './target'; import * as events from '../../aws-events'; import { IRole } from '../../aws-iam'; import { IScheduleTarget, ISchedule, ScheduleTargetInput, ScheduleTargetConfig } from '../../aws-scheduler'; +import { UnscopedValidationError } from '../../core'; /** * An entry to be sent to EventBridge @@ -54,7 +55,7 @@ export class EventBridgePutEvents extends ScheduleTargetBase implements ISchedul ) { super(props, entry.eventBus.eventBusArn); if (this.props.input) { - throw new Error('ScheduleTargetBaseProps.input is not supported for EventBridgePutEvents. Please use entry.detail instead.'); + throw new UnscopedValidationError('ScheduleTargetBaseProps.input is not supported for EventBridgePutEvents. Please use entry.detail instead.'); } } diff --git a/packages/aws-cdk-lib/aws-scheduler-targets/lib/kinesis-stream-put-record.ts b/packages/aws-cdk-lib/aws-scheduler-targets/lib/kinesis-stream-put-record.ts index ec05558f75df3..faf694c81fef9 100644 --- a/packages/aws-cdk-lib/aws-scheduler-targets/lib/kinesis-stream-put-record.ts +++ b/packages/aws-cdk-lib/aws-scheduler-targets/lib/kinesis-stream-put-record.ts @@ -2,7 +2,7 @@ import { ScheduleTargetBase, ScheduleTargetBaseProps } from './target'; import { IRole } from '../../aws-iam'; import * as kinesis from '../../aws-kinesis'; import { ISchedule, IScheduleTarget, ScheduleTargetConfig } from '../../aws-scheduler'; -import { Token } from '../../core'; +import { Token, ValidationError } from '../../core'; /** * Properties for a Kinesis Data Streams Target @@ -29,7 +29,7 @@ export class KinesisStreamPutRecord extends ScheduleTargetBase implements ISched super(props, stream.streamArn); if (!Token.isUnresolved(props.partitionKey) && (props.partitionKey.length < 1 || props.partitionKey.length > 256)) { - throw new Error(`partitionKey length must be between 1 and 256, got ${props.partitionKey.length}`); + throw new ValidationError(`partitionKey length must be between 1 and 256, got ${props.partitionKey.length}`, stream); } } diff --git a/packages/aws-cdk-lib/aws-scheduler-targets/lib/sage-maker-start-pipeline-execution.ts b/packages/aws-cdk-lib/aws-scheduler-targets/lib/sage-maker-start-pipeline-execution.ts index d3d26d40bc42e..180885bbd13f0 100644 --- a/packages/aws-cdk-lib/aws-scheduler-targets/lib/sage-maker-start-pipeline-execution.ts +++ b/packages/aws-cdk-lib/aws-scheduler-targets/lib/sage-maker-start-pipeline-execution.ts @@ -2,6 +2,7 @@ import { ScheduleTargetBase, ScheduleTargetBaseProps } from './target'; import { IRole } from '../../aws-iam'; import { IPipeline } from '../../aws-sagemaker'; import { ISchedule, IScheduleTarget, ScheduleTargetConfig } from '../../aws-scheduler'; +import { ValidationError } from '../../core'; /** * Properties for a pipeline parameter @@ -45,7 +46,7 @@ export class SageMakerStartPipelineExecution extends ScheduleTargetBase implemen super(props, pipeline.pipelineArn); if (props.pipelineParameterList !== undefined && props.pipelineParameterList.length > 200) { - throw new Error(`pipelineParameterList length must be between 0 and 200, got ${props.pipelineParameterList.length}`); + throw new ValidationError(`pipelineParameterList length must be between 0 and 200, got ${props.pipelineParameterList.length}`, pipeline); } } diff --git a/packages/aws-cdk-lib/aws-scheduler-targets/lib/sqs-send-message.ts b/packages/aws-cdk-lib/aws-scheduler-targets/lib/sqs-send-message.ts index 2f7a72d4dd02c..f6c84b739e236 100644 --- a/packages/aws-cdk-lib/aws-scheduler-targets/lib/sqs-send-message.ts +++ b/packages/aws-cdk-lib/aws-scheduler-targets/lib/sqs-send-message.ts @@ -2,7 +2,7 @@ import { ScheduleTargetBase, ScheduleTargetBaseProps } from './target'; import { IRole } from '../../aws-iam'; import { ISchedule, IScheduleTarget, ScheduleTargetConfig } from '../../aws-scheduler'; import * as sqs from '../../aws-sqs'; -import { Token } from '../../core'; +import { Token, ValidationError } from '../../core'; /** * Properties for a SQS Queue Target @@ -35,16 +35,16 @@ export class SqsSendMessage extends ScheduleTargetBase implements IScheduleTarge if (props.messageGroupId !== undefined) { if (!Token.isUnresolved(props.messageGroupId) && (props.messageGroupId.length < 1 || props.messageGroupId.length > 128)) { - throw new Error(`messageGroupId length must be between 1 and 128, got ${props.messageGroupId.length}`); + throw new ValidationError(`messageGroupId length must be between 1 and 128, got ${props.messageGroupId.length}`, queue); } if (!queue.fifo) { - throw new Error('target must be a FIFO queue if messageGroupId is specified'); + throw new ValidationError('target must be a FIFO queue if messageGroupId is specified', queue); } if (!(queue.node.defaultChild as sqs.CfnQueue).contentBasedDeduplication) { - throw new Error('contentBasedDeduplication must be true if the target is a FIFO queue'); + throw new ValidationError('contentBasedDeduplication must be true if the target is a FIFO queue', queue); } } else if (queue.fifo) { - throw new Error('messageGroupId must be specified if the target is a FIFO queue'); + throw new ValidationError('messageGroupId must be specified if the target is a FIFO queue', queue); } } diff --git a/packages/aws-cdk-lib/aws-scheduler-targets/lib/target.ts b/packages/aws-cdk-lib/aws-scheduler-targets/lib/target.ts index af7eb2d925a28..9d7aa2ee9bafd 100644 --- a/packages/aws-cdk-lib/aws-scheduler-targets/lib/target.ts +++ b/packages/aws-cdk-lib/aws-scheduler-targets/lib/target.ts @@ -2,7 +2,7 @@ import * as iam from '../../aws-iam'; import { ISchedule, ScheduleTargetConfig, ScheduleTargetInput } from '../../aws-scheduler'; import { CfnSchedule } from '../../aws-scheduler/lib/scheduler.generated'; import * as sqs from '../../aws-sqs'; -import { Duration, PhysicalName, Stack, Token } from '../../core'; +import { Duration, PhysicalName, Stack, Token, UnscopedValidationError } from '../../core'; import { md5hash } from '../../core/lib/helpers-internal'; /** @@ -157,10 +157,10 @@ export abstract class ScheduleTargetBase { if (maximumEventAge) { maxAge = maximumEventAge.toSeconds({ integral: true }); if (maxAge > maxMaxAge) { - throw new Error('Maximum event age is 1 day'); + throw new UnscopedValidationError('Maximum event age is 1 day'); } if (maxAge < minMaxAge) { - throw new Error('Minimum event age is 1 minute'); + throw new UnscopedValidationError('Minimum event age is 1 minute'); } } let maxAttempts = 185; diff --git a/packages/aws-cdk-lib/aws-scheduler-targets/lib/universal.ts b/packages/aws-cdk-lib/aws-scheduler-targets/lib/universal.ts index d2faf566225e3..2ded0583cd93f 100644 --- a/packages/aws-cdk-lib/aws-scheduler-targets/lib/universal.ts +++ b/packages/aws-cdk-lib/aws-scheduler-targets/lib/universal.ts @@ -1,7 +1,7 @@ import { ScheduleTargetBase, ScheduleTargetBaseProps } from './target'; import { IRole, PolicyStatement } from '../../aws-iam'; import { IScheduleTarget } from '../../aws-scheduler'; -import { Annotations, Aws, Token } from '../../core'; +import { Annotations, Aws, Token, UnscopedValidationError } from '../../core'; import { awsSdkToIamAction } from '../../custom-resources/lib/helpers-internal'; /** @@ -80,13 +80,13 @@ export class Universal extends ScheduleTargetBase implements IScheduleTarget { const action = props.action; if (!Token.isUnresolved(service) && service !== service.toLowerCase()) { - throw new Error(`API service must be lowercase, got: ${service}`); + throw new UnscopedValidationError(`API service must be lowercase, got: ${service}`); } if (!Token.isUnresolved(action) && !action.startsWith(action[0]?.toLowerCase())) { - throw new Error(`API action must be camelCase, got: ${action}`); + throw new UnscopedValidationError(`API action must be camelCase, got: ${action}`); } if (!Token.isUnresolved(action) && NOT_SUPPORTED_ACTION_PREFIX.some(prefix => action.startsWith(prefix))) { - throw new Error(`Read-only API action is not supported by EventBridge Scheduler: ${service}:${action}`); + throw new UnscopedValidationError(`Read-only API action is not supported by EventBridge Scheduler: ${service}:${action}`); } const arn = `arn:${Aws.PARTITION}:scheduler:::aws-sdk:${service}:${action}`; diff --git a/packages/aws-cdk-lib/aws-scheduler/lib/schedule-expression.ts b/packages/aws-cdk-lib/aws-scheduler/lib/schedule-expression.ts index 3765dc8b3eec1..b56362634c8aa 100644 --- a/packages/aws-cdk-lib/aws-scheduler/lib/schedule-expression.ts +++ b/packages/aws-cdk-lib/aws-scheduler/lib/schedule-expression.ts @@ -1,5 +1,5 @@ import * as events from '../../aws-events'; -import { Duration, TimeZone } from '../../core'; +import { Duration, TimeZone, UnscopedValidationError } from '../../core'; /** * ScheduleExpression for EventBridge Schedule @@ -22,7 +22,7 @@ export abstract class ScheduleExpression { return new LiteralScheduleExpression(`at(${literal})`, timeZone ?? TimeZone.ETC_UTC); } catch (e) { if (e instanceof RangeError) { - throw new Error('Invalid date'); + throw new UnscopedValidationError('Invalid date'); } throw e; } diff --git a/packages/aws-cdk-lib/aws-scheduler/lib/schedule.ts b/packages/aws-cdk-lib/aws-scheduler/lib/schedule.ts index e5f0104a63fc2..6726e61701585 100644 --- a/packages/aws-cdk-lib/aws-scheduler/lib/schedule.ts +++ b/packages/aws-cdk-lib/aws-scheduler/lib/schedule.ts @@ -5,7 +5,7 @@ import { CfnSchedule } from './scheduler.generated'; import { IScheduleTarget } from './target'; import * as cloudwatch from '../../aws-cloudwatch'; import * as kms from '../../aws-kms'; -import { Arn, ArnFormat, Duration, IResource, Resource, Token } from '../../core'; +import { Arn, ArnFormat, Duration, IResource, Resource, Token, UnscopedValidationError, ValidationError } from '../../core'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; /** @@ -46,7 +46,7 @@ export class TimeWindow { */ public static flexible(maxWindow: Duration): TimeWindow { if (maxWindow.toMinutes() < 1 || maxWindow.toMinutes() > 1440) { - throw new Error(`The provided duration must be between 1 minute and 1440 minutes, got ${maxWindow.toMinutes()}`); + throw new UnscopedValidationError(`The provided duration must be between 1 minute and 1440 minutes, got ${maxWindow.toMinutes()}`); } return new TimeWindow('FLEXIBLE', maxWindow); } @@ -304,7 +304,7 @@ export class Schedule extends Resource implements ISchedule { this.validateTimeFrame(props.start, props.end); if (props.scheduleName && !Token.isUnresolved(props.scheduleName) && props.scheduleName.length > 64) { - throw new Error(`scheduleName cannot be longer than 64 characters, got: ${props.scheduleName.length}`); + throw new ValidationError(`scheduleName cannot be longer than 64 characters, got: ${props.scheduleName.length}`, this); } const resource = new CfnSchedule(this, 'Resource', { @@ -349,10 +349,10 @@ export class Schedule extends Resource implements ISchedule { }; if (policy.maximumEventAgeInSeconds && (policy.maximumEventAgeInSeconds < 60 || policy.maximumEventAgeInSeconds > 86400)) { - throw new Error(`maximumEventAgeInSeconds must be between 60 and 86400, got ${policy.maximumEventAgeInSeconds}`); + throw new ValidationError(`maximumEventAgeInSeconds must be between 60 and 86400, got ${policy.maximumEventAgeInSeconds}`, this); } if (policy.maximumRetryAttempts && (policy.maximumRetryAttempts < 0 || policy.maximumRetryAttempts > 185)) { - throw new Error(`maximumRetryAttempts must be between 0 and 185, got ${policy.maximumRetryAttempts}`); + throw new ValidationError(`maximumRetryAttempts must be between 0 and 185, got ${policy.maximumRetryAttempts}`, this); } const isEmptyPolicy = Object.values(policy).every(value => value === undefined); @@ -361,7 +361,7 @@ export class Schedule extends Resource implements ISchedule { private validateTimeFrame(start?: Date, end?: Date) { if (start && end && start >= end) { - throw new Error(`start must precede end, got start: ${start.toISOString()}, end: ${end.toISOString()}`); + throw new ValidationError(`start must precede end, got start: ${start.toISOString()}, end: ${end.toISOString()}`, this); } } }