From f2bcb5fca94946525329a07302f519be004afb49 Mon Sep 17 00:00:00 2001 From: Kaizen Conroy Date: Fri, 21 Mar 2025 17:52:48 -0400 Subject: [PATCH] feat(docdb): throw `ValidationErrors` instead of untyped Errors --- packages/aws-cdk-lib/.eslintrc.js | 1 + packages/aws-cdk-lib/aws-docdb/lib/cluster.ts | 34 +++++++++---------- .../aws-cdk-lib/aws-docdb/lib/endpoint.ts | 4 +-- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/packages/aws-cdk-lib/.eslintrc.js b/packages/aws-cdk-lib/.eslintrc.js index 41c1e87ec77ee..8e9fc7059e236 100644 --- a/packages/aws-cdk-lib/.eslintrc.js +++ b/packages/aws-cdk-lib/.eslintrc.js @@ -46,6 +46,7 @@ const enableNoThrowDefaultErrorIn = [ 'aws-codepipeline', 'aws-codepipeline-actions', 'aws-cognito', + 'aws-docdb', 'aws-ecr', 'aws-elasticloadbalancing', 'aws-elasticloadbalancingv2', diff --git a/packages/aws-cdk-lib/aws-docdb/lib/cluster.ts b/packages/aws-cdk-lib/aws-docdb/lib/cluster.ts index 145c99ded29de..a98cb16dfbf8d 100644 --- a/packages/aws-cdk-lib/aws-docdb/lib/cluster.ts +++ b/packages/aws-cdk-lib/aws-docdb/lib/cluster.ts @@ -11,7 +11,7 @@ import * as kms from '../../aws-kms'; import * as logs from '../../aws-logs'; import { CaCertificate } from '../../aws-rds'; import * as secretsmanager from '../../aws-secretsmanager'; -import { CfnResource, Duration, RemovalPolicy, Resource, Token } from '../../core'; +import { CfnResource, Duration, RemovalPolicy, Resource, Token, UnscopedValidationError, ValidationError } from '../../core'; import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource'; const MIN_ENGINE_VERSION_FOR_IO_OPTIMIZED_STORAGE = 5; @@ -355,35 +355,35 @@ export class DatabaseCluster extends DatabaseClusterBase { public get instanceIdentifiers(): string[] { if (!this._instanceIdentifiers) { - throw new Error('Cannot access `instanceIdentifiers` of an imported cluster without provided instanceIdentifiers'); + throw new UnscopedValidationError('Cannot access `instanceIdentifiers` of an imported cluster without provided instanceIdentifiers'); } return this._instanceIdentifiers; } public get clusterEndpoint(): Endpoint { if (!this._clusterEndpoint) { - throw new Error('Cannot access `clusterEndpoint` of an imported cluster without an endpoint address and port'); + throw new UnscopedValidationError('Cannot access `clusterEndpoint` of an imported cluster without an endpoint address and port'); } return this._clusterEndpoint; } public get clusterReadEndpoint(): Endpoint { if (!this._clusterReadEndpoint) { - throw new Error('Cannot access `clusterReadEndpoint` of an imported cluster without a readerEndpointAddress and port'); + throw new UnscopedValidationError('Cannot access `clusterReadEndpoint` of an imported cluster without a readerEndpointAddress and port'); } return this._clusterReadEndpoint; } public get instanceEndpoints(): Endpoint[] { if (!this._instanceEndpoints) { - throw new Error('Cannot access `instanceEndpoints` of an imported cluster without instanceEndpointAddresses and port'); + throw new UnscopedValidationError('Cannot access `instanceEndpoints` of an imported cluster without instanceEndpointAddresses and port'); } return this._instanceEndpoints; } public get securityGroupId(): string { if (!this._securityGroupId) { - throw new Error('Cannot access `securityGroupId` of an imported cluster without securityGroupId'); + throw new UnscopedValidationError('Cannot access `securityGroupId` of an imported cluster without securityGroupId'); } return this._securityGroupId; } @@ -479,7 +479,7 @@ export class DatabaseCluster extends DatabaseClusterBase { // We cannot test whether the subnets are in different AZs, but at least we can test the amount. // See https://docs.aws.amazon.com/documentdb/latest/developerguide/replication.html#replication.high-availability if (subnetIds.length < 2) { - throw new Error(`Cluster requires at least 2 subnets, got ${subnetIds.length}`); + throw new ValidationError(`Cluster requires at least 2 subnets, got ${subnetIds.length}`, this); } const subnetGroup = new CfnDBSubnetGroup(this, 'Subnets', { @@ -497,7 +497,7 @@ export class DatabaseCluster extends DatabaseClusterBase { vpc: this.vpc, }); // HACK: Use an escape-hatch to apply a consistent removal policy to the - // security group so we don't get errors when trying to delete the stack. + // security group so we don't get ValidationErrors when trying to delete the stack. const securityGroupRemovalPolicy = this.getSecurityGroupRemovalPolicy(props); (securityGroup.node.defaultChild as CfnResource).applyRemovalPolicy(securityGroupRemovalPolicy, { applyToUpdateReplacePolicy: true, @@ -529,12 +529,12 @@ export class DatabaseCluster extends DatabaseClusterBase { const storageEncrypted = props.storageEncrypted ?? true; if (props.kmsKey && !storageEncrypted) { - throw new Error('KMS key supplied but storageEncrypted is false'); + throw new ValidationError('KMS key supplied but storageEncrypted is false', this); } const validEngineVersionRegex = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$/; if (props.engineVersion !== undefined && !validEngineVersionRegex.test(props.engineVersion)) { - throw new Error(`Invalid engine version: '${props.engineVersion}'. Engine version must be in the format x.y.z`); + throw new ValidationError(`Invalid engine version: '${props.engineVersion}'. Engine version must be in the format x.y.z`, this); } if ( @@ -542,7 +542,7 @@ export class DatabaseCluster extends DatabaseClusterBase { && props.engineVersion !== undefined && Number(props.engineVersion.split('.')[0]) < MIN_ENGINE_VERSION_FOR_IO_OPTIMIZED_STORAGE ) { - throw new Error(`I/O-optimized storage is supported starting with engine version 5.0.0, got '${props.engineVersion}'`); + throw new ValidationError(`I/O-optimized storage is supported starting with engine version 5.0.0, got '${props.engineVersion}'`, this); } // Create the DocDB cluster @@ -594,7 +594,7 @@ export class DatabaseCluster extends DatabaseClusterBase { // Create the instances const instanceCount = props.instances ?? DatabaseCluster.DEFAULT_NUM_INSTANCES; if (instanceCount < 1) { - throw new Error('At least one instance is required'); + throw new ValidationError('At least one instance is required', this); } const instanceRemovalPolicy = this.getInstanceRemovalPolicy(props); @@ -651,7 +651,7 @@ export class DatabaseCluster extends DatabaseClusterBase { private getInstanceRemovalPolicy(props: DatabaseClusterProps) { if (props.instanceRemovalPolicy === RemovalPolicy.SNAPSHOT) { - throw new Error('AWS::DocDB::DBInstance does not support the SNAPSHOT removal policy'); + throw new ValidationError('AWS::DocDB::DBInstance does not support the SNAPSHOT removal policy', this); } if (props.instanceRemovalPolicy) return props.instanceRemovalPolicy; return !props.removalPolicy || props.removalPolicy !== RemovalPolicy.SNAPSHOT ? @@ -660,7 +660,7 @@ export class DatabaseCluster extends DatabaseClusterBase { private getSecurityGroupRemovalPolicy(props: DatabaseClusterProps) { if (props.securityGroupRemovalPolicy === RemovalPolicy.SNAPSHOT) { - throw new Error('AWS::EC2::SecurityGroup does not support the SNAPSHOT removal policy'); + throw new ValidationError('AWS::EC2::SecurityGroup does not support the SNAPSHOT removal policy', this); } if (props.securityGroupRemovalPolicy) return props.securityGroupRemovalPolicy; return !props.removalPolicy || props.removalPolicy !== RemovalPolicy.SNAPSHOT ? @@ -676,13 +676,13 @@ export class DatabaseCluster extends DatabaseClusterBase { @MethodMetadata() public addRotationSingleUser(automaticallyAfter?: Duration): secretsmanager.SecretRotation { if (!this.secret) { - throw new Error('Cannot add single user rotation for a cluster without secret.'); + throw new ValidationError('Cannot add single user rotation for a cluster without secret.', this); } const id = 'RotationSingleUser'; const existing = this.node.tryFindChild(id); if (existing) { - throw new Error('A single user rotation was already added to this cluster.'); + throw new ValidationError('A single user rotation was already added to this cluster.', this); } return new secretsmanager.SecretRotation(this, id, { @@ -702,7 +702,7 @@ export class DatabaseCluster extends DatabaseClusterBase { @MethodMetadata() public addRotationMultiUser(id: string, options: RotationMultiUserOptions): secretsmanager.SecretRotation { if (!this.secret) { - throw new Error('Cannot add multi user rotation for a cluster without secret.'); + throw new ValidationError('Cannot add multi user rotation for a cluster without secret.', this); } return new secretsmanager.SecretRotation(this, id, { secret: options.secret, diff --git a/packages/aws-cdk-lib/aws-docdb/lib/endpoint.ts b/packages/aws-cdk-lib/aws-docdb/lib/endpoint.ts index 02e6e813c2a6d..3837dd631ee58 100644 --- a/packages/aws-cdk-lib/aws-docdb/lib/endpoint.ts +++ b/packages/aws-cdk-lib/aws-docdb/lib/endpoint.ts @@ -1,4 +1,4 @@ -import { Token } from '../../core'; +import { Token, UnscopedValidationError } from '../../core'; /** * Connection endpoint of a database cluster or instance @@ -52,7 +52,7 @@ export class Endpoint { */ constructor(address: string, port: number) { if (!Token.isUnresolved(port) && !Endpoint.isValidPort(port)) { - throw new Error(`Port must be an integer between [${Endpoint.MIN_PORT}, ${Endpoint.MAX_PORT}] but got: ${port}`); + throw new UnscopedValidationError(`Port must be an integer between [${Endpoint.MIN_PORT}, ${Endpoint.MAX_PORT}] but got: ${port}`); } this.hostname = address;