Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(autoscaling): throw ValidationError instead of untyped Errors #33388

Merged
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat(autoscaling): throw ValidationError instead of untyped Errors
mrgrain committed Feb 11, 2025

Verified

This commit was signed with the committer’s verified signature.
mrgrain Momo Kornher
commit 2c5e030629c6b1cdbb7914222c41e6bf1a48d09d
2 changes: 2 additions & 0 deletions packages/aws-cdk-lib/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -16,6 +16,8 @@ baseConfig.rules['import/no-extraneous-dependencies'] = [

// no-throw-default-error
const enableNoThrowDefaultErrorIn = [
'aws-autoscaling',
'aws-autoscaling-common',
'aws-amplify',
'aws-amplifyuibuilder',
'aws-apigateway',
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ScalingInterval } from './types';
import { UnscopedValidationError } from '../../core';

export interface CompleteScalingInterval {
readonly lower: number;
@@ -30,12 +31,12 @@ export function normalizeIntervals(intervals: ScalingInterval[], changesAreAbsol
*/
function orderAndCompleteIntervals(intervals: ScalingInterval[]): CompleteScalingInterval[] {
if (intervals.length < 2) {
throw new Error('Require at least 2 intervals');
throw new UnscopedValidationError('Require at least 2 intervals');
}

for (const interval of intervals) {
if (interval.lower === undefined && interval.upper === undefined) {
throw new Error(`Must supply at least one of 'upper' or 'lower', got: ${JSON.stringify(interval)}`);
throw new UnscopedValidationError(`Must supply at least one of 'upper' or 'lower', got: ${JSON.stringify(interval)}`);
}
}

@@ -55,7 +56,7 @@ function orderAndCompleteIntervals(intervals: ScalingInterval[]): CompleteScalin
if (intervals[lastIndex].upper === undefined) { intervals[lastIndex] = { ...intervals[lastIndex], upper: Infinity }; }
for (const interval of intervals) {
if (interval.lower === undefined || interval.upper === undefined) {
throw new Error(`Could not determine the lower and upper bounds for ${JSON.stringify(interval)}`);
throw new UnscopedValidationError(`Could not determine the lower and upper bounds for ${JSON.stringify(interval)}`);
}
}

@@ -64,7 +65,7 @@ function orderAndCompleteIntervals(intervals: ScalingInterval[]): CompleteScalin
// Validate that we have nonoverlapping intervals now.
for (let i = 0; i < completeIntervals.length - 1; i++) {
if (overlap(completeIntervals[i], completeIntervals[i + 1])) {
throw new Error(`Two intervals overlap: ${JSON.stringify(completeIntervals[i])} and ${JSON.stringify(completeIntervals[i + 1])}`);
throw new UnscopedValidationError(`Two intervals overlap: ${JSON.stringify(completeIntervals[i])} and ${JSON.stringify(completeIntervals[i + 1])}`);
}
}

@@ -148,7 +149,7 @@ function combineUndefineds(intervals: CompleteScalingInterval[]) {
function validateAtMostOneUndefined(intervals: CompleteScalingInterval[]) {
const undef = intervals.filter(x => x.change === undefined);
if (undef.length > 1) {
throw new Error(`Can have at most one no-change interval, got ${JSON.stringify(undef)}`);
throw new UnscopedValidationError(`Can have at most one no-change interval, got ${JSON.stringify(undef)}`);
}
}

108 changes: 54 additions & 54 deletions packages/aws-cdk-lib/aws-autoscaling/lib/auto-scaling-group.ts

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions packages/aws-cdk-lib/aws-autoscaling/lib/lifecycle-hook.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import { IAutoScalingGroup } from './auto-scaling-group';
import { CfnLifecycleHook } from './autoscaling.generated';
import { ILifecycleHookTarget } from './lifecycle-hook-target';
import * as iam from '../../aws-iam';
import { Duration, IResource, Resource } from '../../core';
import { Duration, IResource, Resource, ValidationError } from '../../core';
import { addConstructMetadata } from '../../core/lib/metadata-resource';

/**
@@ -97,7 +97,7 @@ export class LifecycleHook extends Resource implements ILifecycleHook {
*/
public get role() {
if (!this._role) {
throw new Error('\'role\' is undefined. Please specify a \'role\' or specify a \'notificationTarget\' to have a role provided for you.');
throw new ValidationError('\'role\' is undefined. Please specify a \'role\' or specify a \'notificationTarget\' to have a role provided for you.', this);
}

return this._role;
@@ -122,7 +122,7 @@ export class LifecycleHook extends Resource implements ILifecycleHook {
this._role = props.role;

if (!props.notificationTarget) {
throw new Error("'notificationTarget' parameter required when 'role' parameter is specified");
throw new ValidationError("'notificationTarget' parameter required when 'role' parameter is specified", this);
}
} else {
this._role = targetProps ? targetProps.createdRole : undefined;
4 changes: 2 additions & 2 deletions packages/aws-cdk-lib/aws-autoscaling/lib/schedule.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Construct } from 'constructs';
import { Annotations } from '../../core';
import { Annotations, UnscopedValidationError } from '../../core';

/**
* Schedule for scheduled scaling actions
@@ -20,7 +20,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, '*');
4 changes: 2 additions & 2 deletions packages/aws-cdk-lib/aws-autoscaling/lib/scheduled-action.ts
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import { Construct } from 'constructs';
import { IAutoScalingGroup } from './auto-scaling-group';
import { CfnScheduledAction } from './autoscaling.generated';
import { Schedule } from './schedule';
import { Resource } from '../../core';
import { Resource, ValidationError } from '../../core';
import { addConstructMetadata } from '../../core/lib/metadata-resource';

/**
@@ -104,7 +104,7 @@ export class ScheduledAction extends Resource {
addConstructMetadata(this, props);

if (props.minCapacity === undefined && props.maxCapacity === undefined && props.desiredCapacity === undefined) {
throw new Error('At least one of minCapacity, maxCapacity, or desiredCapacity is required');
throw new ValidationError('At least one of minCapacity, maxCapacity, or desiredCapacity is required', this);
}

// add a warning on synth when minute is not defined in a cron schedule
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Construct } from 'constructs';
import { IAutoScalingGroup } from './auto-scaling-group';
import { CfnScalingPolicy } from './autoscaling.generated';
import { Annotations, Duration, Lazy } from '../../core';
import { Annotations, Duration, Lazy, ValidationError } from '../../core';

/**
* Properties for a scaling policy
@@ -97,7 +97,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,
14 changes: 7 additions & 7 deletions packages/aws-cdk-lib/aws-autoscaling/lib/step-scaling-policy.ts
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import { IAutoScalingGroup } from './auto-scaling-group';
import { AdjustmentType, MetricAggregationType, StepScalingAction } from './step-scaling-action';
import { findAlarmThresholds, normalizeIntervals } from '../../aws-autoscaling-common';
import * as cloudwatch from '../../aws-cloudwatch';
import { Duration, Token } from '../../core';
import { Duration, Token, ValidationError } from '../../core';

export interface BasicStepScalingPolicyProps {
/**
@@ -112,28 +112,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', this);
}

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}`, this);
}

if (props.evaluationPeriods !== undefined && !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}`, this);
}
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', this);
}
if (!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}`, this);
}
if (!Token.isUnresolved(props.datapointsToAlarm)
&& !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}`, this);
}
}

Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ import { Construct } from 'constructs';
import { IAutoScalingGroup } from './auto-scaling-group';
import { CfnScalingPolicy } from './autoscaling.generated';
import * as cloudwatch from '../../aws-cloudwatch';
import { Duration } from '../../core';
import { Duration, ValidationError } from '../../core';

/**
* Base interface for target tracking props
@@ -110,20 +110,20 @@ export class TargetTrackingScalingPolicy extends Construct {
private resource: CfnScalingPolicy;

constructor(scope: Construct, id: string, props: TargetTrackingScalingPolicyProps) {
super(scope, id);

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.', this);
}

if (props.predefinedMetric === PredefinedMetric.ALB_REQUEST_COUNT_PER_TARGET && !props.resourceLabel) {
throw new Error('When tracking the ALBRequestCountPerTarget metric, the ALB identifier must be supplied in resourceLabel');
throw new ValidationError('When tracking the ALBRequestCountPerTarget metric, the ALB identifier must be supplied in resourceLabel', this);
}

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.', this);
}

super(scope, id);

this.resource = new CfnScalingPolicy(this, 'Resource', {
policyType: 'TargetTrackingScaling',
autoScalingGroupName: props.autoScalingGroup.autoScalingGroupName,
4 changes: 3 additions & 1 deletion packages/aws-cdk-lib/aws-autoscaling/lib/volume.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@
// existed first in the "autoscaling" module before it existed in the "ec2"
// module so we couldn't standardize the structs in the right way.

import { UnscopedValidationError } from '../../core';

/**
* Block device
*/
@@ -154,7 +156,7 @@ export class BlockDeviceVolume {
*/
public static ephemeral(volumeIndex: number) {
if (volumeIndex < 0) {
throw new Error(`volumeIndex must be a number starting from 0, got "${volumeIndex}"`);
throw new UnscopedValidationError(`volumeIndex must be a number starting from 0, got "${volumeIndex}"`);
}

return new this(undefined, `ephemeral${volumeIndex}`);
6 changes: 3 additions & 3 deletions packages/aws-cdk-lib/aws-autoscaling/lib/warm-pool.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Construct } from 'constructs';
import { IAutoScalingGroup } from './auto-scaling-group';
import { CfnWarmPool } from './autoscaling.generated';
import { Lazy, Names, Resource } from '../../core';
import { Lazy, Names, Resource, ValidationError } from '../../core';
import { addConstructMetadata } from '../../core/lib/metadata-resource';

/**
@@ -64,11 +64,11 @@ export class WarmPool extends Resource {
addConstructMetadata(this, props);

if (props.maxGroupPreparedCapacity && props.maxGroupPreparedCapacity < -1) {
throw new Error('\'maxGroupPreparedCapacity\' parameter should be greater than or equal to -1');
throw new ValidationError('\'maxGroupPreparedCapacity\' parameter should be greater than or equal to -1', this);
}

if (props.minSize && props.minSize < 0) {
throw new Error('\'minSize\' parameter should be greater than or equal to 0');
throw new ValidationError('\'minSize\' parameter should be greater than or equal to 0', this);
}

new CfnWarmPool(this, 'Resource', {