From abd4a3e997e2a456d31b9bb4669b22e12c364c01 Mon Sep 17 00:00:00 2001 From: Hogan Bobertz Date: Mon, 27 Jan 2025 11:10:15 -0500 Subject: [PATCH] feat(applicationautoscaling): throw `ValidationError` instead of untyped errors (#33172) ### Issue `aws-applicationautoscaling ` for #32569 ### Description of changes ValidationErrors everywhere ### Describe any new or updated permissions being added n/a ### Description of how you validated changes Existing tests. Exemptions granted as this is basically a refactor of existing code. ### 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* --- packages/aws-cdk-lib/.eslintrc.js | 1 + .../lib/scalable-target.ts | 9 +++++---- .../aws-applicationautoscaling/lib/schedule.ts | 7 ++++--- .../lib/step-scaling-action.ts | 3 ++- .../lib/step-scaling-policy.ts | 13 +++++++------ .../lib/target-tracking-scaling-policy.ts | 11 ++++++----- 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/packages/aws-cdk-lib/.eslintrc.js b/packages/aws-cdk-lib/.eslintrc.js index dfc6debd186de..6d19b0d5ffb38 100644 --- a/packages/aws-cdk-lib/.eslintrc.js +++ b/packages/aws-cdk-lib/.eslintrc.js @@ -22,6 +22,7 @@ const enableNoThrowDefaultErrorIn = [ 'aws-apigatewayv2', 'aws-apigatewayv2-authorizers', 'aws-apigatewayv2-integrations', + 'aws-applicationautoscaling', 'aws-cognito', 'aws-elasticloadbalancing', 'aws-elasticloadbalancingv2', diff --git a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/scalable-target.ts b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/scalable-target.ts index e816785de48af..c42db197d9707 100644 --- a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/scalable-target.ts +++ b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/scalable-target.ts @@ -5,6 +5,7 @@ import { BasicStepScalingPolicyProps, StepScalingPolicy } from './step-scaling-p import { BasicTargetTrackingScalingPolicyProps, TargetTrackingScalingPolicy } from './target-tracking-scaling-policy'; import * as iam from '../../aws-iam'; import { IResource, Lazy, Resource, TimeZone, withResolved } from '../../core'; +import { ValidationError } from '../../core/lib/errors'; export interface IScalableTarget extends IResource { /** @@ -100,19 +101,19 @@ export class ScalableTarget extends Resource implements IScalableTarget { withResolved(props.maxCapacity, max => { if (max < 0) { - throw new RangeError(`maxCapacity cannot be negative, got: ${props.maxCapacity}`); + throw new ValidationError(`maxCapacity cannot be negative, got: ${props.maxCapacity}`, scope); } }); withResolved(props.minCapacity, min => { if (min < 0) { - throw new RangeError(`minCapacity cannot be negative, got: ${props.minCapacity}`); + throw new ValidationError(`minCapacity cannot be negative, got: ${props.minCapacity}`, scope); } }); withResolved(props.minCapacity, props.maxCapacity, (min, max) => { if (max < min) { - throw new RangeError(`minCapacity (${props.minCapacity}) should be lower than maxCapacity (${props.maxCapacity})`); + throw new ValidationError(`minCapacity (${props.minCapacity}) should be lower than maxCapacity (${props.maxCapacity})`, scope); } }); @@ -145,7 +146,7 @@ export class ScalableTarget extends Resource implements IScalableTarget { */ public scaleOnSchedule(id: string, action: ScalingSchedule) { if (action.minCapacity === undefined && action.maxCapacity === undefined) { - throw new Error(`You must supply at least one of minCapacity or maxCapacity, got ${JSON.stringify(action)}`); + throw new ValidationError(`You must supply at least one of minCapacity or maxCapacity, got ${JSON.stringify(action)}`, this); } // add a warning on synth when minute is not defined in a cron schedule diff --git a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts index 82c73291993ed..c0c832060a987 100644 --- a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts +++ b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/schedule.ts @@ -1,5 +1,6 @@ import { Construct } from 'constructs'; import { Annotations, Duration } from '../../core'; +import { UnscopedValidationError } from '../../core/lib/errors'; /** * Schedule for scheduled scaling actions @@ -21,12 +22,12 @@ export abstract class Schedule { if (duration.isUnresolved()) { const validDurationUnit = ['minute', 'minutes', 'hour', 'hours', 'day', 'days']; if (!validDurationUnit.includes(duration.unitLabel())) { - throw new Error("Allowed units for scheduling are: 'minute', 'minutes', 'hour', 'hours', 'day' or 'days'"); + throw new UnscopedValidationError("Allowed units for scheduling are: 'minute', 'minutes', 'hour', 'hours', 'day' or 'days'"); } return new LiteralSchedule(`rate(${duration.formatTokenToNumber()})`); } if (duration.toSeconds() === 0) { - throw new Error('Duration cannot be 0'); + throw new UnscopedValidationError('Duration cannot be 0'); } let rate = maybeRate(duration.toDays({ integral: false }), 'day'); @@ -47,7 +48,7 @@ export abstract class Schedule { */ public static cron(options: CronOptions): Schedule { if (options.weekDay !== undefined && options.day !== undefined) { - throw new Error('Cannot supply both \'day\' and \'weekDay\', use at most one'); + throw new UnscopedValidationError('Cannot supply both \'day\' and \'weekDay\', use at most one'); } const minute = fallback(options.minute, '*'); diff --git a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/step-scaling-action.ts b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/step-scaling-action.ts index afeaa6c471748..dddfd26425b6d 100644 --- a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/step-scaling-action.ts +++ b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/step-scaling-action.ts @@ -2,6 +2,7 @@ import { Construct } from 'constructs'; import { CfnScalingPolicy } from './applicationautoscaling.generated'; import { IScalableTarget } from './scalable-target'; import * as cdk from '../../core'; +import { ValidationError } from '../../core/lib/errors'; /** * Properties for a scaling policy @@ -102,7 +103,7 @@ export class StepScalingAction extends Construct { */ public addAdjustment(adjustment: AdjustmentTier) { if (adjustment.lowerBound === undefined && adjustment.upperBound === undefined) { - throw new Error('At least one of lowerBound or upperBound is required'); + throw new ValidationError('At least one of lowerBound or upperBound is required', this); } this.adjustments.push({ metricIntervalLowerBound: adjustment.lowerBound, diff --git a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/step-scaling-policy.ts b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/step-scaling-policy.ts index 09f8fb8f3f669..196402f60d0ac 100644 --- a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/step-scaling-policy.ts +++ b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/step-scaling-policy.ts @@ -4,6 +4,7 @@ import { AdjustmentType, MetricAggregationType, StepScalingAction } from './step import { findAlarmThresholds, normalizeIntervals } from '../../aws-autoscaling-common'; import * as cloudwatch from '../../aws-cloudwatch'; import * as cdk from '../../core'; +import { ValidationError } from '../../core/lib/errors'; export interface BasicStepScalingPolicyProps { /** @@ -110,28 +111,28 @@ export class StepScalingPolicy extends Construct { super(scope, id); if (props.scalingSteps.length < 2) { - throw new Error('You must supply at least 2 intervals for autoscaling'); + throw new ValidationError('You must supply at least 2 intervals for autoscaling', scope); } if (props.scalingSteps.length > 40) { - throw new Error(`'scalingSteps' can have at most 40 steps, got ${props.scalingSteps.length}`); + throw new ValidationError(`'scalingSteps' can have at most 40 steps, got ${props.scalingSteps.length}`, scope); } if (props.evaluationPeriods !== undefined && !cdk.Token.isUnresolved(props.evaluationPeriods) && props.evaluationPeriods < 1) { - throw new Error(`evaluationPeriods cannot be less than 1, got: ${props.evaluationPeriods}`); + throw new ValidationError(`evaluationPeriods cannot be less than 1, got: ${props.evaluationPeriods}`, scope); } if (props.datapointsToAlarm !== undefined) { if (props.evaluationPeriods === undefined) { - throw new Error('evaluationPeriods must be set if datapointsToAlarm is set'); + throw new ValidationError('evaluationPeriods must be set if datapointsToAlarm is set', scope); } if (!cdk.Token.isUnresolved(props.datapointsToAlarm) && props.datapointsToAlarm < 1) { - throw new Error(`datapointsToAlarm cannot be less than 1, got: ${props.datapointsToAlarm}`); + throw new ValidationError(`datapointsToAlarm cannot be less than 1, got: ${props.datapointsToAlarm}`, scope); } if (!cdk.Token.isUnresolved(props.datapointsToAlarm) && !cdk.Token.isUnresolved(props.evaluationPeriods) && props.evaluationPeriods < props.datapointsToAlarm ) { - throw new Error(`datapointsToAlarm must be less than or equal to evaluationPeriods, got datapointsToAlarm: ${props.datapointsToAlarm}, evaluationPeriods: ${props.evaluationPeriods}`); + throw new ValidationError(`datapointsToAlarm must be less than or equal to evaluationPeriods, got datapointsToAlarm: ${props.datapointsToAlarm}, evaluationPeriods: ${props.evaluationPeriods}`, scope); } } diff --git a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts index 313c32bc1e7a1..5e6e8fa24b7e9 100644 --- a/packages/aws-cdk-lib/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts +++ b/packages/aws-cdk-lib/aws-applicationautoscaling/lib/target-tracking-scaling-policy.ts @@ -3,6 +3,7 @@ import { CfnScalingPolicy } from './applicationautoscaling.generated'; import { IScalableTarget } from './scalable-target'; import * as cloudwatch from '../../aws-cloudwatch'; import * as cdk from '../../core'; +import { ValidationError } from '../../core/lib/errors'; /** * Base interface for target tracking props @@ -123,11 +124,11 @@ export class TargetTrackingScalingPolicy extends Construct { constructor(scope: Construct, id: string, props: TargetTrackingScalingPolicyProps) { if ((props.customMetric === undefined) === (props.predefinedMetric === undefined)) { - throw new Error('Exactly one of \'customMetric\' or \'predefinedMetric\' must be specified.'); + throw new ValidationError('Exactly one of \'customMetric\' or \'predefinedMetric\' must be specified.', scope); } if (props.customMetric && !props.customMetric.toMetricConfig().metricStat) { - throw new Error('Only direct metrics are supported for Target Tracking. Use Step Scaling or supply a Metric object.'); + throw new ValidationError('Only direct metrics are supported for Target Tracking. Use Step Scaling or supply a Metric object.', scope); } super(scope, id); @@ -142,7 +143,7 @@ export class TargetTrackingScalingPolicy extends Construct { policyType: 'TargetTrackingScaling', scalingTargetId: props.scalingTarget.scalableTargetId, targetTrackingScalingPolicyConfiguration: { - customizedMetricSpecification: renderCustomMetric(props.customMetric), + customizedMetricSpecification: renderCustomMetric(this, props.customMetric), disableScaleIn: props.disableScaleIn, predefinedMetricSpecification: predefinedMetric !== undefined ? { predefinedMetricType: predefinedMetric, @@ -158,12 +159,12 @@ export class TargetTrackingScalingPolicy extends Construct { } } -function renderCustomMetric(metric?: cloudwatch.IMetric): CfnScalingPolicy.CustomizedMetricSpecificationProperty | undefined { +function renderCustomMetric(scope: Construct, metric?: cloudwatch.IMetric): CfnScalingPolicy.CustomizedMetricSpecificationProperty | undefined { if (!metric) { return undefined; } const c = metric.toMetricConfig().metricStat!; if (c.statistic.startsWith('p')) { - throw new Error(`Cannot use statistic '${c.statistic}' for Target Tracking: only 'Average', 'Minimum', 'Maximum', 'SampleCount', and 'Sum' are supported.`); + throw new ValidationError(`Cannot use statistic '${c.statistic}' for Target Tracking: only 'Average', 'Minimum', 'Maximum', 'SampleCount', and 'Sum' are supported.`, scope); } return {