Skip to content

Commit 0b2db62

Browse files
authoredJan 22, 2025··
feat(rds): throw ValidationError instead of untyped errors (#33042)
### Issue `aws-rds` 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*
1 parent ed7d54d commit 0b2db62

11 files changed

+111
-101
lines changed
 

‎packages/aws-cdk-lib/.eslintrc.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ baseConfig.rules['import/no-extraneous-dependencies'] = [
1515

1616

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

‎packages/aws-cdk-lib/aws-rds/lib/aurora-cluster-instance.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as ec2 from '../../aws-ec2';
1111
import { IRole } from '../../aws-iam';
1212
import * as kms from '../../aws-kms';
1313
import { IResource, Resource, Duration, RemovalPolicy, ArnFormat, FeatureFlags } from '../../core';
14+
import { ValidationError } from '../../core/lib/errors';
1415
import { AURORA_CLUSTER_CHANGE_SCOPE_OF_INSTANCE_PARAMETER_GROUP_WITH_EACH_PARAMETERS } from '../../cx-api';
1516

1617
/**
@@ -476,7 +477,7 @@ class AuroraClusterInstance extends Resource implements IAuroraClusterInstance {
476477
});
477478
this.tier = props.promotionTier ?? 2;
478479
if (this.tier > 15) {
479-
throw new Error('promotionTier must be between 0-15');
480+
throw new ValidationError('promotionTier must be between 0-15', this);
480481
}
481482

482483
const isOwnedResource = Resource.isOwnedResource(props.cluster);
@@ -499,7 +500,7 @@ class AuroraClusterInstance extends Resource implements IAuroraClusterInstance {
499500
const enablePerformanceInsights = props.enablePerformanceInsights
500501
|| props.performanceInsightRetention !== undefined || props.performanceInsightEncryptionKey !== undefined;
501502
if (enablePerformanceInsights && props.enablePerformanceInsights === false) {
502-
throw new Error('`enablePerformanceInsights` disabled, but `performanceInsightRetention` or `performanceInsightEncryptionKey` was set');
503+
throw new ValidationError('`enablePerformanceInsights` disabled, but `performanceInsightRetention` or `performanceInsightEncryptionKey` was set', this);
503504
}
504505

505506
this.performanceInsightsEnabled = enablePerformanceInsights;

‎packages/aws-cdk-lib/aws-rds/lib/cluster-engine.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { EngineVersion } from './engine-version';
44
import { IParameterGroup, ParameterGroup } from './parameter-group';
55
import * as iam from '../../aws-iam';
66
import * as secretsmanager from '../../aws-secretsmanager';
7+
import { ValidationError } from '../../core/lib/errors';
78

89
/**
910
* The extra options passed to the `IClusterEngine.bindToCluster` method.
@@ -1179,19 +1180,19 @@ class AuroraPostgresClusterEngine extends ClusterEngineBase {
11791180
// skip validation for unversioned as it might be supported/unsupported. we cannot reliably tell at compile-time
11801181
if (this.engineVersion?.fullVersion) {
11811182
if (options.s3ImportRole && !(config.features?.s3Import)) {
1182-
throw new Error(`s3Import is not supported for Postgres version: ${this.engineVersion.fullVersion}. Use a version that supports the s3Import feature.`);
1183+
throw new ValidationError(`s3Import is not supported for Postgres version: ${this.engineVersion.fullVersion}. Use a version that supports the s3Import feature.`, scope);
11831184
}
11841185
if (options.s3ExportRole && !(config.features?.s3Export)) {
1185-
throw new Error(`s3Export is not supported for Postgres version: ${this.engineVersion.fullVersion}. Use a version that supports the s3Export feature.`);
1186+
throw new ValidationError(`s3Export is not supported for Postgres version: ${this.engineVersion.fullVersion}. Use a version that supports the s3Export feature.`, scope);
11861187
}
11871188
}
11881189
return config;
11891190
}
11901191

11911192
protected defaultParameterGroup(scope: Construct): IParameterGroup | undefined {
11921193
if (!this.parameterGroupFamily) {
1193-
throw new Error('Could not create a new ParameterGroup for an unversioned aurora-postgresql cluster engine. ' +
1194-
'Please either use a versioned engine, or pass an explicit ParameterGroup when creating the cluster');
1194+
throw new ValidationError('Could not create a new ParameterGroup for an unversioned aurora-postgresql cluster engine. ' +
1195+
'Please either use a versioned engine, or pass an explicit ParameterGroup when creating the cluster', scope);
11951196
}
11961197
return ParameterGroup.fromParameterGroupName(scope, 'AuroraPostgreSqlDatabaseClusterEngineDefaultParameterGroup',
11971198
`default.${this.parameterGroupFamily}`);

‎packages/aws-cdk-lib/aws-rds/lib/cluster.ts

+40-39
Large diffs are not rendered by default.

‎packages/aws-cdk-lib/aws-rds/lib/instance-engine.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { EngineVersion } from './engine-version';
44
import { IOptionGroup, OptionGroup } from './option-group';
55
import * as iam from '../../aws-iam';
66
import * as secretsmanager from '../../aws-secretsmanager';
7+
import { ValidationError } from '../../core/lib/errors';
78

89
/**
910
* The options passed to `IInstanceEngine.bind`.
@@ -142,9 +143,9 @@ abstract class InstanceEngineBase implements IInstanceEngine {
142143
this.engineFamily = props.engineFamily;
143144
}
144145

145-
public bindToInstance(_scope: Construct, options: InstanceEngineBindOptions): InstanceEngineConfig {
146+
public bindToInstance(scope: Construct, options: InstanceEngineBindOptions): InstanceEngineConfig {
146147
if (options.timezone && !this.supportsTimezone) {
147-
throw new Error(`timezone property can not be configured for ${this.engineType}`);
148+
throw new ValidationError(`timezone property can not be configured for ${this.engineType}`, scope);
148149
}
149150
return {
150151
features: this.features,
@@ -621,7 +622,7 @@ class MariaDbInstanceEngine extends InstanceEngineBase {
621622

622623
public bindToInstance(scope: Construct, options: InstanceEngineBindOptions): InstanceEngineConfig {
623624
if (options.domain) {
624-
throw new Error(`domain property cannot be configured for ${this.engineType}`);
625+
throw new ValidationError(`domain property cannot be configured for ${this.engineType}`, scope);
625626
}
626627
return super.bindToInstance(scope, options);
627628
}
@@ -2828,7 +2829,7 @@ abstract class SqlServerInstanceEngineBase extends InstanceEngineBase {
28282829
const s3Role = options.s3ImportRole ?? options.s3ExportRole;
28292830
if (s3Role) {
28302831
if (options.s3ImportRole && options.s3ExportRole && options.s3ImportRole !== options.s3ExportRole) {
2831-
throw new Error('S3 import and export roles must be the same for SQL Server engines');
2832+
throw new ValidationError('S3 import and export roles must be the same for SQL Server engines', scope);
28322833
}
28332834

28342835
if (!optionGroup) {

‎packages/aws-cdk-lib/aws-rds/lib/instance.ts

+18-17
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import * as logs from '../../aws-logs';
1818
import * as s3 from '../../aws-s3';
1919
import * as secretsmanager from '../../aws-secretsmanager';
2020
import { ArnComponents, ArnFormat, Duration, FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Token, Tokenization } from '../../core';
21+
import { ValidationError } from '../../core/lib/errors';
2122
import * as cxapi from '../../cx-api';
2223

2324
/**
@@ -180,15 +181,15 @@ export abstract class DatabaseInstanceBase extends Resource implements IDatabase
180181

181182
public grantConnect(grantee: iam.IGrantable, dbUser?: string): iam.Grant {
182183
if (this.enableIamAuthentication === false) {
183-
throw new Error('Cannot grant connect when IAM authentication is disabled');
184+
throw new ValidationError('Cannot grant connect when IAM authentication is disabled', this);
184185
}
185186

186187
if (!this.instanceResourceId) {
187-
throw new Error('For imported Database Instances, instanceResourceId is required to grantConnect()');
188+
throw new ValidationError('For imported Database Instances, instanceResourceId is required to grantConnect()', this);
188189
}
189190

190191
if (!dbUser) {
191-
throw new Error('For imported Database Instances, the dbUser is required to grantConnect()');
192+
throw new ValidationError('For imported Database Instances, the dbUser is required to grantConnect()', this);
192193
}
193194

194195
this.enableIamAuthentication = true;
@@ -784,12 +785,12 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData
784785

785786
this.vpc = props.vpc;
786787
if (props.vpcSubnets && props.vpcPlacement) {
787-
throw new Error('Only one of `vpcSubnets` or `vpcPlacement` can be specified');
788+
throw new ValidationError('Only one of `vpcSubnets` or `vpcPlacement` can be specified', this);
788789
}
789790
this.vpcPlacement = props.vpcSubnets ?? props.vpcPlacement;
790791

791792
if (props.multiAz === true && props.availabilityZone) {
792-
throw new Error('Requesting a specific availability zone is not valid for Multi-AZ instances');
793+
throw new ValidationError('Requesting a specific availability zone is not valid for Multi-AZ instances', this);
793794
}
794795

795796
const subnetGroup = props.subnetGroup ?? new SubnetGroup(this, 'SubnetGroup', {
@@ -820,12 +821,12 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData
820821
const storageType = props.storageType ?? StorageType.GP2;
821822
const iops = defaultIops(storageType, props.iops);
822823
if (props.storageThroughput && storageType !== StorageType.GP3) {
823-
throw new Error(`The storage throughput can only be specified with GP3 storage type. Got ${storageType}.`);
824+
throw new ValidationError(`The storage throughput can only be specified with GP3 storage type. Got ${storageType}.`, this);
824825
}
825826
if (storageType === StorageType.GP3 && props.storageThroughput && iops
826827
&& !Token.isUnresolved(props.storageThroughput) && !Token.isUnresolved(iops)
827828
&& props.storageThroughput/iops > 0.25) {
828-
throw new Error(`The maximum ratio of storage throughput to IOPS is 0.25. Got ${props.storageThroughput/iops}.`);
829+
throw new ValidationError(`The maximum ratio of storage throughput to IOPS is 0.25. Got ${props.storageThroughput/iops}.`, this);
829830
}
830831

831832
this.cloudwatchLogGroups = {};
@@ -837,7 +838,7 @@ abstract class DatabaseInstanceNew extends DatabaseInstanceBase implements IData
837838
const enablePerformanceInsights = props.enablePerformanceInsights
838839
|| props.performanceInsightRetention !== undefined || props.performanceInsightEncryptionKey !== undefined;
839840
if (enablePerformanceInsights && props.enablePerformanceInsights === false) {
840-
throw new Error('`enablePerformanceInsights` disabled, but `performanceInsightRetention` or `performanceInsightEncryptionKey` was set');
841+
throw new ValidationError('`enablePerformanceInsights` disabled, but `performanceInsightRetention` or `performanceInsightEncryptionKey` was set', this);
841842
}
842843

843844
if (props.domain) {
@@ -1019,13 +1020,13 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa
10191020
const engineFeatures = engineConfig.features;
10201021
if (s3ImportRole) {
10211022
if (!engineFeatures?.s3Import) {
1022-
throw new Error(`Engine '${engineDescription(props.engine)}' does not support S3 import`);
1023+
throw new ValidationError(`Engine '${engineDescription(props.engine)}' does not support S3 import`, this);
10231024
}
10241025
instanceAssociatedRoles.push({ roleArn: s3ImportRole.roleArn, featureName: engineFeatures?.s3Import });
10251026
}
10261027
if (s3ExportRole) {
10271028
if (!engineFeatures?.s3Export) {
1028-
throw new Error(`Engine '${engineDescription(props.engine)}' does not support S3 export`);
1029+
throw new ValidationError(`Engine '${engineDescription(props.engine)}' does not support S3 export`, this);
10291030
}
10301031
// only add the export feature if it's different from the import feature
10311032
if (engineFeatures.s3Import !== engineFeatures?.s3Export) {
@@ -1036,7 +1037,7 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa
10361037
this.instanceType = props.instanceType ?? ec2.InstanceType.of(ec2.InstanceClass.M5, ec2.InstanceSize.LARGE);
10371038

10381039
if (props.parameterGroup && props.parameters) {
1039-
throw new Error('You cannot specify both parameterGroup and parameters');
1040+
throw new ValidationError('You cannot specify both parameterGroup and parameters', this);
10401041
}
10411042

10421043
const dbParameterGroupName = props.parameters
@@ -1069,13 +1070,13 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa
10691070
*/
10701071
public addRotationSingleUser(options: RotationSingleUserOptions = {}): secretsmanager.SecretRotation {
10711072
if (!this.secret) {
1072-
throw new Error('Cannot add single user rotation for an instance without secret.');
1073+
throw new ValidationError('Cannot add single user rotation for an instance without secret.', this);
10731074
}
10741075

10751076
const id = 'RotationSingleUser';
10761077
const existing = this.node.tryFindChild(id);
10771078
if (existing) {
1078-
throw new Error('A single user rotation was already added to this instance.');
1079+
throw new ValidationError('A single user rotation was already added to this instance.', this);
10791080
}
10801081

10811082
return new secretsmanager.SecretRotation(this, id, {
@@ -1092,7 +1093,7 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa
10921093
*/
10931094
public addRotationMultiUser(id: string, options: RotationMultiUserOptions): secretsmanager.SecretRotation {
10941095
if (!this.secret) {
1095-
throw new Error('Cannot add multi user rotation for an instance without secret.');
1096+
throw new ValidationError('Cannot add multi user rotation for an instance without secret.', this);
10961097
}
10971098

10981099
return new secretsmanager.SecretRotation(this, id, {
@@ -1115,7 +1116,7 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa
11151116
public grantConnect(grantee: iam.IGrantable, dbUser?: string): iam.Grant {
11161117
if (!dbUser) {
11171118
if (!this.secret) {
1118-
throw new Error('A secret or dbUser is required to grantConnect()');
1119+
throw new ValidationError('A secret or dbUser is required to grantConnect()', this);
11191120
}
11201121

11211122
dbUser = this.secret.secretValueFromJson('username').unsafeUnwrap();
@@ -1248,7 +1249,7 @@ export class DatabaseInstanceFromSnapshot extends DatabaseInstanceSource impleme
12481249
let secret = credentials?.secret;
12491250
if (!secret && credentials?.generatePassword) {
12501251
if (!credentials.username) {
1251-
throw new Error('`credentials` `username` must be specified when `generatePassword` is set to true');
1252+
throw new ValidationError('`credentials` `username` must be specified when `generatePassword` is set to true', this);
12521253
}
12531254

12541255
secret = new DatabaseSecret(this, 'Secret', {
@@ -1351,7 +1352,7 @@ export class DatabaseInstanceReadReplica extends DatabaseInstanceNew implements
13511352
if (props.sourceDatabaseInstance.engine
13521353
&& !props.sourceDatabaseInstance.engine.supportsReadReplicaBackups
13531354
&& props.backupRetention) {
1354-
throw new Error(`Cannot set 'backupRetention', as engine '${engineDescription(props.sourceDatabaseInstance.engine)}' does not support automatic backups for read replicas`);
1355+
throw new ValidationError(`Cannot set 'backupRetention', as engine '${engineDescription(props.sourceDatabaseInstance.engine)}' does not support automatic backups for read replicas`, this);
13551356
}
13561357

13571358
// The read replica instance always uses the same engine as the source instance

‎packages/aws-cdk-lib/aws-rds/lib/option-group.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { IInstanceEngine } from './instance-engine';
33
import { CfnOptionGroup } from './rds.generated';
44
import * as ec2 from '../../aws-ec2';
55
import { IResource, Lazy, Resource } from '../../core';
6+
import { ValidationError } from '../../core/lib/errors';
67

78
/**
89
* An option group
@@ -126,7 +127,7 @@ export class OptionGroup extends Resource implements IOptionGroup {
126127

127128
const majorEngineVersion = props.engine.engineVersion?.majorVersion;
128129
if (!majorEngineVersion) {
129-
throw new Error("OptionGroup cannot be used with an engine that doesn't specify a version");
130+
throw new ValidationError("OptionGroup cannot be used with an engine that doesn't specify a version", this);
130131
}
131132

132133
props.configurations.forEach(config => this.addConfiguration(config));
@@ -146,7 +147,7 @@ export class OptionGroup extends Resource implements IOptionGroup {
146147

147148
if (configuration.port) {
148149
if (!configuration.vpc) {
149-
throw new Error('`port` and `vpc` must be specified together.');
150+
throw new ValidationError('`port` and `vpc` must be specified together.', this);
150151
}
151152

152153
const securityGroups = configuration.securityGroups && configuration.securityGroups.length > 0

‎packages/aws-cdk-lib/aws-rds/lib/parameter-group.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Construct } from 'constructs';
22
import { IEngine } from './engine';
33
import { CfnDBClusterParameterGroup, CfnDBParameterGroup } from './rds.generated';
44
import { IResource, Lazy, RemovalPolicy, Resource } from '../../core';
5+
import { ValidationError } from '../../core/lib/errors';
56

67
/**
78
* Options for `IParameterGroup.bindToCluster`.
@@ -143,7 +144,7 @@ export class ParameterGroup extends Resource implements IParameterGroup {
143144

144145
const family = props.engine.parameterGroupFamily;
145146
if (!family) {
146-
throw new Error("ParameterGroup cannot be used with an engine that doesn't specify a version");
147+
throw new ValidationError("ParameterGroup cannot be used with an engine that doesn't specify a version", this);
147148
}
148149
this.family = family;
149150
this.description = props.description;

‎packages/aws-cdk-lib/aws-rds/lib/private/util.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as ec2 from '../../../aws-ec2';
33
import * as iam from '../../../aws-iam';
44
import * as s3 from '../../../aws-s3';
55
import { RemovalPolicy } from '../../../core';
6+
import { ValidationError } from '../../../core/lib/errors';
67
import { DatabaseSecret } from '../database-secret';
78
import { IEngine } from '../engine';
89
import { CommonRotationUserOptions, Credentials, SnapshotCredentials } from '../props';
@@ -43,7 +44,7 @@ export function setupS3ImportExport(
4344

4445
if (props.s3ImportBuckets && props.s3ImportBuckets.length > 0) {
4546
if (props.s3ImportRole) {
46-
throw new Error('Only one of s3ImportRole or s3ImportBuckets must be specified, not both.');
47+
throw new ValidationError('Only one of s3ImportRole or s3ImportBuckets must be specified, not both.', scope);
4748
}
4849

4950
s3ImportRole = (combineRoles && s3ExportRole) ? s3ExportRole : new iam.Role(scope, 'S3ImportRole', {
@@ -56,7 +57,7 @@ export function setupS3ImportExport(
5657

5758
if (props.s3ExportBuckets && props.s3ExportBuckets.length > 0) {
5859
if (props.s3ExportRole) {
59-
throw new Error('Only one of s3ExportRole or s3ExportBuckets must be specified, not both.');
60+
throw new ValidationError('Only one of s3ExportRole or s3ExportBuckets must be specified, not both.', scope);
6061
}
6162

6263
s3ExportRole = (combineRoles && s3ImportRole) ? s3ImportRole : new iam.Role(scope, 'S3ExportRole', {
@@ -117,7 +118,7 @@ export function renderSnapshotCredentials(scope: Construct, credentials?: Snapsh
117118
let secret = renderedCredentials?.secret;
118119
if (!secret && renderedCredentials?.generatePassword) {
119120
if (!renderedCredentials.username) {
120-
throw new Error('`snapshotCredentials` `username` must be specified when `generatePassword` is set to true');
121+
throw new ValidationError('`snapshotCredentials` `username` must be specified when `generatePassword` is set to true', scope);
121122
}
122123

123124
renderedCredentials = SnapshotCredentials.fromSecret(

‎packages/aws-cdk-lib/aws-rds/lib/proxy.ts

+13-12
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as ec2 from '../../aws-ec2';
88
import * as iam from '../../aws-iam';
99
import * as secretsmanager from '../../aws-secretsmanager';
1010
import * as cdk from '../../core';
11+
import { ValidationError } from '../../core/lib/errors';
1112
import * as cxapi from '../../cx-api';
1213

1314
/**
@@ -98,14 +99,14 @@ export class ProxyTarget {
9899

99100
if (!engine) {
100101
const errorResource = this.dbCluster ?? this.dbInstance;
101-
throw new Error(`Could not determine engine for proxy target '${errorResource?.node.path}'. ` +
102-
'Please provide it explicitly when importing the resource');
102+
throw new ValidationError(`Could not determine engine for proxy target '${errorResource?.node.path}'. ` +
103+
'Please provide it explicitly when importing the resource', proxy);
103104
}
104105

105106
const engineFamily = engine.engineFamily;
106107
if (!engineFamily) {
107-
throw new Error('RDS proxies require an engine family to be specified on the database cluster or instance. ' +
108-
`No family specified for engine '${engineDescription(engine)}'`);
108+
throw new ValidationError('RDS proxies require an engine family to be specified on the database cluster or instance. ' +
109+
`No family specified for engine '${engineDescription(engine)}'`, proxy);
109110
}
110111

111112
// allow connecting to the Cluster/Instance from the Proxy
@@ -374,7 +375,7 @@ abstract class DatabaseProxyBase extends cdk.Resource implements IDatabaseProxy
374375

375376
public grantConnect(grantee: iam.IGrantable, dbUser?: string): iam.Grant {
376377
if (!dbUser) {
377-
throw new Error('For imported Database Proxies, the dbUser is required in grantConnect()');
378+
throw new ValidationError('For imported Database Proxies, the dbUser is required in grantConnect()', this);
378379
}
379380
const scopeStack = cdk.Stack.of(this);
380381
const proxyGeneratedId = scopeStack.splitArn(this.dbProxyArn, cdk.ArnFormat.COLON_RESOURCE_NAME).resourceName;
@@ -474,7 +475,7 @@ export class DatabaseProxy extends DatabaseProxyBase
474475
const bindResult = props.proxyTarget.bind(this);
475476

476477
if (props.secrets.length < 1) {
477-
throw new Error('One or more secrets are required.');
478+
throw new ValidationError('One or more secrets are required.', this);
478479
}
479480
this.secrets = props.secrets;
480481

@@ -515,7 +516,7 @@ export class DatabaseProxy extends DatabaseProxyBase
515516
}
516517

517518
if (!!dbInstanceIdentifiers && !!dbClusterIdentifiers) {
518-
throw new Error('Cannot specify both dbInstanceIdentifiers and dbClusterIdentifiers');
519+
throw new ValidationError('Cannot specify both dbInstanceIdentifiers and dbClusterIdentifiers', this);
519520
}
520521

521522
const proxyTargetGroup = new CfnDBProxyTargetGroup(this, 'ProxyTargetGroup', {
@@ -565,7 +566,7 @@ export class DatabaseProxy extends DatabaseProxyBase
565566
public grantConnect(grantee: iam.IGrantable, dbUser?: string): iam.Grant {
566567
if (!dbUser) {
567568
if (this.secrets.length > 1) {
568-
throw new Error('When the Proxy contains multiple Secrets, you must pass a dbUser explicitly to grantConnect()');
569+
throw new ValidationError('When the Proxy contains multiple Secrets, you must pass a dbUser explicitly to grantConnect()', this);
569570
}
570571
// 'username' is the field RDS uses here,
571572
// see https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/rds-proxy.html#rds-proxy-secrets-arns
@@ -577,16 +578,16 @@ export class DatabaseProxy extends DatabaseProxyBase
577578
private validateClientPasswordAuthType(engineFamily: string, clientPasswordAuthType?: ClientPasswordAuthType) {
578579
if (!clientPasswordAuthType || cdk.Token.isUnresolved(clientPasswordAuthType)) return;
579580
if (clientPasswordAuthType === ClientPasswordAuthType.MYSQL_NATIVE_PASSWORD && engineFamily !== 'MYSQL') {
580-
throw new Error(`${ClientPasswordAuthType.MYSQL_NATIVE_PASSWORD} client password authentication type requires MYSQL engineFamily, got ${engineFamily}`);
581+
throw new ValidationError(`${ClientPasswordAuthType.MYSQL_NATIVE_PASSWORD} client password authentication type requires MYSQL engineFamily, got ${engineFamily}`, this);
581582
}
582583
if (clientPasswordAuthType === ClientPasswordAuthType.POSTGRES_SCRAM_SHA_256 && engineFamily !== 'POSTGRESQL') {
583-
throw new Error(`${ClientPasswordAuthType.POSTGRES_SCRAM_SHA_256} client password authentication type requires POSTGRESQL engineFamily, got ${engineFamily}`);
584+
throw new ValidationError(`${ClientPasswordAuthType.POSTGRES_SCRAM_SHA_256} client password authentication type requires POSTGRESQL engineFamily, got ${engineFamily}`, this);
584585
}
585586
if (clientPasswordAuthType === ClientPasswordAuthType.POSTGRES_MD5 && engineFamily !== 'POSTGRESQL') {
586-
throw new Error(`${ClientPasswordAuthType.POSTGRES_MD5} client password authentication type requires POSTGRESQL engineFamily, got ${engineFamily}`);
587+
throw new ValidationError(`${ClientPasswordAuthType.POSTGRES_MD5} client password authentication type requires POSTGRESQL engineFamily, got ${engineFamily}`, this);
587588
}
588589
if (clientPasswordAuthType === ClientPasswordAuthType.SQL_SERVER_AUTHENTICATION && engineFamily !== 'SQLSERVER') {
589-
throw new Error(`${ClientPasswordAuthType.SQL_SERVER_AUTHENTICATION} client password authentication type requires SQLSERVER engineFamily, got ${engineFamily}`);
590+
throw new ValidationError(`${ClientPasswordAuthType.SQL_SERVER_AUTHENTICATION} client password authentication type requires SQLSERVER engineFamily, got ${engineFamily}`, this);
590591
}
591592
}
592593
}

‎packages/aws-cdk-lib/aws-rds/lib/serverless-cluster.ts

+17-16
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import * as iam from '../../aws-iam';
1313
import * as kms from '../../aws-kms';
1414
import * as secretsmanager from '../../aws-secretsmanager';
1515
import { Resource, Duration, Token, Annotations, RemovalPolicy, IResource, Stack, Lazy, FeatureFlags, ArnFormat } from '../../core';
16+
import { ValidationError } from '../../core/lib/errors';
1617
import * as cxapi from '../../cx-api';
1718

1819
/**
@@ -361,7 +362,7 @@ abstract class ServerlessClusterBase extends Resource implements IServerlessClus
361362
*/
362363
public grantDataApiAccess(grantee: iam.IGrantable): iam.Grant {
363364
if (this.enableDataApi === false) {
364-
throw new Error('Cannot grant Data API access when the Data API is disabled');
365+
throw new ValidationError('Cannot grant Data API access when the Data API is disabled', this);
365366
}
366367

367368
this.enableDataApi = true;
@@ -402,13 +403,13 @@ abstract class ServerlessClusterNew extends ServerlessClusterBase {
402403

403404
if (props.vpc === undefined) {
404405
if (props.vpcSubnets !== undefined) {
405-
throw new Error('A VPC is required to use vpcSubnets in ServerlessCluster. Please add a VPC or remove vpcSubnets');
406+
throw new ValidationError('A VPC is required to use vpcSubnets in ServerlessCluster. Please add a VPC or remove vpcSubnets', this);
406407
}
407408
if (props.subnetGroup !== undefined) {
408-
throw new Error('A VPC is required to use subnetGroup in ServerlessCluster. Please add a VPC or remove subnetGroup');
409+
throw new ValidationError('A VPC is required to use subnetGroup in ServerlessCluster. Please add a VPC or remove subnetGroup', this);
409410
}
410411
if (props.securityGroups !== undefined) {
411-
throw new Error('A VPC is required to use securityGroups in ServerlessCluster. Please add a VPC or remove securityGroups');
412+
throw new ValidationError('A VPC is required to use securityGroups in ServerlessCluster. Please add a VPC or remove securityGroups', this);
412413
}
413414
}
414415

@@ -440,7 +441,7 @@ abstract class ServerlessClusterNew extends ServerlessClusterBase {
440441
if (props.backupRetention) {
441442
const backupRetentionDays = props.backupRetention.toDays();
442443
if (backupRetentionDays < 1 || backupRetentionDays > 35) {
443-
throw new Error(`backup retention period must be between 1 and 35 days. received: ${backupRetentionDays}`);
444+
throw new ValidationError(`backup retention period must be between 1 and 35 days. received: ${backupRetentionDays}`, this);
444445
}
445446
}
446447

@@ -484,16 +485,16 @@ abstract class ServerlessClusterNew extends ServerlessClusterBase {
484485
const timeout = options.timeout?.toSeconds();
485486

486487
if (minCapacity && maxCapacity && minCapacity > maxCapacity) {
487-
throw new Error('maximum capacity must be greater than or equal to minimum capacity.');
488+
throw new ValidationError('maximum capacity must be greater than or equal to minimum capacity.', this);
488489
}
489490

490491
const secondsToAutoPause = options.autoPause?.toSeconds();
491492
if (secondsToAutoPause && (secondsToAutoPause < 300 || secondsToAutoPause > 86400)) {
492-
throw new Error('auto pause time must be between 5 minutes and 1 day.');
493+
throw new ValidationError('auto pause time must be between 5 minutes and 1 day.', this);
493494
}
494495

495496
if (timeout && (timeout < 60 || timeout > 600)) {
496-
throw new Error(`timeout must be between 60 and 600 seconds, but got ${timeout} seconds.`);
497+
throw new ValidationError(`timeout must be between 60 and 600 seconds, but got ${timeout} seconds.`, this);
497498
}
498499

499500
return {
@@ -595,17 +596,17 @@ export class ServerlessCluster extends ServerlessClusterNew {
595596
*/
596597
public addRotationSingleUser(options: RotationSingleUserOptions = {}): secretsmanager.SecretRotation {
597598
if (!this.secret) {
598-
throw new Error('Cannot add single user rotation for a cluster without secret.');
599+
throw new ValidationError('Cannot add single user rotation for a cluster without secret.', this);
599600
}
600601

601602
if (this.vpc === undefined) {
602-
throw new Error('Cannot add single user rotation for a cluster without VPC.');
603+
throw new ValidationError('Cannot add single user rotation for a cluster without VPC.', this);
603604
}
604605

605606
const id = 'RotationSingleUser';
606607
const existing = this.node.tryFindChild(id);
607608
if (existing) {
608-
throw new Error('A single user rotation was already added to this cluster.');
609+
throw new ValidationError('A single user rotation was already added to this cluster.', this);
609610
}
610611

611612
return new secretsmanager.SecretRotation(this, id, {
@@ -622,11 +623,11 @@ export class ServerlessCluster extends ServerlessClusterNew {
622623
*/
623624
public addRotationMultiUser(id: string, options: RotationMultiUserOptions): secretsmanager.SecretRotation {
624625
if (!this.secret) {
625-
throw new Error('Cannot add multi user rotation for a cluster without secret.');
626+
throw new ValidationError('Cannot add multi user rotation for a cluster without secret.', this);
626627
}
627628

628629
if (this.vpc === undefined) {
629-
throw new Error('Cannot add multi user rotation for a cluster without VPC.');
630+
throw new ValidationError('Cannot add multi user rotation for a cluster without VPC.', this);
630631
}
631632

632633
return new secretsmanager.SecretRotation(this, id, {
@@ -673,14 +674,14 @@ class ImportedServerlessCluster extends ServerlessClusterBase implements IServer
673674

674675
public get clusterEndpoint() {
675676
if (!this._clusterEndpoint) {
676-
throw new Error('Cannot access `clusterEndpoint` of an imported cluster without an endpoint address and port');
677+
throw new ValidationError('Cannot access `clusterEndpoint` of an imported cluster without an endpoint address and port', this);
677678
}
678679
return this._clusterEndpoint;
679680
}
680681

681682
public get clusterReadEndpoint() {
682683
if (!this._clusterReadEndpoint) {
683-
throw new Error('Cannot access `clusterReadEndpoint` of an imported cluster without a readerEndpointAddress and port');
684+
throw new ValidationError('Cannot access `clusterReadEndpoint` of an imported cluster without a readerEndpointAddress and port', this);
684685
}
685686
return this._clusterReadEndpoint;
686687
}
@@ -728,7 +729,7 @@ export class ServerlessClusterFromSnapshot extends ServerlessClusterNew {
728729
let secret = credentials?.secret;
729730
if (!secret && credentials?.generatePassword) {
730731
if (!credentials.username) {
731-
throw new Error('`credentials` `username` must be specified when `generatePassword` is set to true');
732+
throw new ValidationError('`credentials` `username` must be specified when `generatePassword` is set to true', this);
732733
}
733734

734735
secret = new DatabaseSecret(this, 'Secret', {

0 commit comments

Comments
 (0)
Please sign in to comment.