diff --git a/packages/aws-cdk-lib/.eslintrc.js b/packages/aws-cdk-lib/.eslintrc.js index eebbf16821843..49c329dbd1b17 100644 --- a/packages/aws-cdk-lib/.eslintrc.js +++ b/packages/aws-cdk-lib/.eslintrc.js @@ -27,6 +27,13 @@ const enableNoThrowDefaultErrorIn = [ 'aws-ssmquicksetup', 'aws-apigatewayv2-authorizers', 'aws-synthetics', + 'aws-route53', + 'aws-route53-patterns', + 'aws-route53-targets', + 'aws-route53profiles', + 'aws-route53recoverycontrol', + 'aws-route53recoveryreadiness', + 'aws-route53resolver', 'aws-s3-assets', 'aws-s3-deployment', 'aws-s3-notifications', diff --git a/packages/aws-cdk-lib/aws-route53-patterns/lib/website-redirect.ts b/packages/aws-cdk-lib/aws-route53-patterns/lib/website-redirect.ts index aedd43a9f5117..a9ba11d9aca74 100644 --- a/packages/aws-cdk-lib/aws-route53-patterns/lib/website-redirect.ts +++ b/packages/aws-cdk-lib/aws-route53-patterns/lib/website-redirect.ts @@ -5,6 +5,7 @@ import { ARecord, AaaaRecord, IHostedZone, RecordTarget } from '../../aws-route5 import { CloudFrontTarget } from '../../aws-route53-targets'; import { BlockPublicAccess, Bucket, RedirectProtocol } from '../../aws-s3'; import { ArnFormat, RemovalPolicy, Stack, Token, FeatureFlags } from '../../core'; +import { ValidationError } from '../../core/lib/errors'; import { md5hash } from '../../core/lib/helpers-internal'; import { ROUTE53_PATTERNS_USE_CERTIFICATE } from '../../cx-api'; @@ -61,7 +62,7 @@ export class HttpsRedirect extends Construct { if (props.certificate) { const certificateRegion = Stack.of(this).splitArn(props.certificate.certificateArn, ArnFormat.SLASH_RESOURCE_NAME).region; if (!Token.isUnresolved(certificateRegion) && certificateRegion !== 'us-east-1') { - throw new Error(`The certificate must be in the us-east-1 region and the certificate you provided is in ${certificateRegion}.`); + throw new ValidationError(`The certificate must be in the us-east-1 region and the certificate you provided is in ${certificateRegion}.`, this); } } const redirectCert = props.certificate ?? this.createCertificate(domainNames, props.zone); @@ -123,10 +124,10 @@ export class HttpsRedirect extends Construct { const stack = Stack.of(this); const parent = stack.node.scope; if (!parent) { - throw new Error(`Stack ${stack.stackId} must be created in the scope of an App or Stage`); + throw new ValidationError(`Stack ${stack.stackId} must be created in the scope of an App or Stage`, this); } if (Token.isUnresolved(stack.region)) { - throw new Error(`When ${ROUTE53_PATTERNS_USE_CERTIFICATE} is enabled, a region must be defined on the Stack`); + throw new ValidationError(`When ${ROUTE53_PATTERNS_USE_CERTIFICATE} is enabled, a region must be defined on the Stack`, this); } if (stack.region !== 'us-east-1') { const stackId = `certificate-redirect-stack-${stack.node.addr}`; diff --git a/packages/aws-cdk-lib/aws-route53-targets/lib/api-gateway-domain-name.ts b/packages/aws-cdk-lib/aws-route53-targets/lib/api-gateway-domain-name.ts index 5e20b107156db..b0ea117dd1a16 100644 --- a/packages/aws-cdk-lib/aws-route53-targets/lib/api-gateway-domain-name.ts +++ b/packages/aws-cdk-lib/aws-route53-targets/lib/api-gateway-domain-name.ts @@ -1,5 +1,6 @@ import * as apig from '../../aws-apigateway'; import * as route53 from '../../aws-route53'; +import { ValidationError } from '../../core/lib/errors'; /** * Defines an API Gateway domain name as the alias target. @@ -28,7 +29,7 @@ export class ApiGatewayDomain implements route53.IAliasRecordTarget { export class ApiGateway extends ApiGatewayDomain { constructor(api: apig.RestApiBase) { if (!api.domainName) { - throw new Error('API does not define a default domain name'); + throw new ValidationError('API does not define a default domain name', api); } super(api.domainName); diff --git a/packages/aws-cdk-lib/aws-route53-targets/lib/bucket-website-target.ts b/packages/aws-cdk-lib/aws-route53-targets/lib/bucket-website-target.ts index fef4aad569cc4..017b4bf59a876 100644 --- a/packages/aws-cdk-lib/aws-route53-targets/lib/bucket-website-target.ts +++ b/packages/aws-cdk-lib/aws-route53-targets/lib/bucket-website-target.ts @@ -2,6 +2,7 @@ import { IAliasRecordTargetProps } from './shared'; import * as route53 from '../../aws-route53'; import * as s3 from '../../aws-s3'; import { Stack, Token } from '../../core'; +import { ValidationError } from '../../core/lib/errors'; import { RegionInfo } from '../../region-info'; /** @@ -10,21 +11,21 @@ import { RegionInfo } from '../../region-info'; export class BucketWebsiteTarget implements route53.IAliasRecordTarget { constructor(private readonly bucket: s3.IBucket, private readonly props?: IAliasRecordTargetProps) {} - public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { + public bind(record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { const { region } = Stack.of(this.bucket.stack); if (Token.isUnresolved(region)) { - throw new Error([ + throw new ValidationError([ 'Cannot use an S3 record alias in region-agnostic stacks.', 'You must specify a specific region when you define the stack', '(see https://docs.aws.amazon.com/cdk/latest/guide/environments.html)', - ].join(' ')); + ].join(' '), record); } const { s3StaticWebsiteHostedZoneId: hostedZoneId, s3StaticWebsiteEndpoint: dnsName } = RegionInfo.get(region); if (!hostedZoneId || !dnsName) { - throw new Error(`Bucket website target is not supported for the "${region}" region`); + throw new ValidationError(`Bucket website target is not supported for the "${region}" region`, record); } return { hostedZoneId, dnsName, evaluateTargetHealth: this.props?.evaluateTargetHealth }; diff --git a/packages/aws-cdk-lib/aws-route53-targets/lib/elastic-beanstalk-environment-target.ts b/packages/aws-cdk-lib/aws-route53-targets/lib/elastic-beanstalk-environment-target.ts index e35ea7b69e8ec..55e671865e711 100644 --- a/packages/aws-cdk-lib/aws-route53-targets/lib/elastic-beanstalk-environment-target.ts +++ b/packages/aws-cdk-lib/aws-route53-targets/lib/elastic-beanstalk-environment-target.ts @@ -1,6 +1,7 @@ import { IAliasRecordTargetProps } from './shared'; import * as route53 from '../../aws-route53'; import * as cdk from '../../core'; +import { ValidationError } from '../../core/lib/errors'; import { RegionInfo } from '../../region-info'; /** @@ -13,9 +14,9 @@ import { RegionInfo } from '../../region-info'; export class ElasticBeanstalkEnvironmentEndpointTarget implements route53.IAliasRecordTarget { constructor( private readonly environmentEndpoint: string, private readonly props?: IAliasRecordTargetProps) {} - public bind(_record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { + public bind(record: route53.IRecordSet, _zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { if (cdk.Token.isUnresolved(this.environmentEndpoint)) { - throw new Error('Cannot use an EBS alias as `environmentEndpoint`. You must find your EBS environment endpoint via the AWS console. See the Elastic Beanstalk developer guide: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customdomains.html'); + throw new ValidationError('Cannot use an EBS alias as `environmentEndpoint`. You must find your EBS environment endpoint via the AWS console. See the Elastic Beanstalk developer guide: https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/customdomains.html', record); } const dnsName = this.environmentEndpoint; @@ -25,7 +26,7 @@ export class ElasticBeanstalkEnvironmentEndpointTarget implements route53.IAlias const { ebsEnvEndpointHostedZoneId: hostedZoneId } = RegionInfo.get(region); if (!hostedZoneId || !dnsName) { - throw new Error(`Elastic Beanstalk environment target is not supported for the "${region}" region.`); + throw new ValidationError(`Elastic Beanstalk environment target is not supported for the "${region}" region.`, record); } return { diff --git a/packages/aws-cdk-lib/aws-route53-targets/lib/route53-record.ts b/packages/aws-cdk-lib/aws-route53-targets/lib/route53-record.ts index 1235a04ee0d61..0c796f8562468 100644 --- a/packages/aws-cdk-lib/aws-route53-targets/lib/route53-record.ts +++ b/packages/aws-cdk-lib/aws-route53-targets/lib/route53-record.ts @@ -1,4 +1,5 @@ import * as route53 from '../../aws-route53'; +import { ValidationError } from '../../core/lib/errors'; /** * Use another Route 53 record as an alias record target @@ -7,9 +8,9 @@ export class Route53RecordTarget implements route53.IAliasRecordTarget { constructor(private readonly record: route53.IRecordSet) { } - public bind(_record: route53.IRecordSet, zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { + public bind(record: route53.IRecordSet, zone?: route53.IHostedZone): route53.AliasRecordTargetConfig { if (!zone) { // zone introduced as optional to avoid a breaking change - throw new Error('Cannot bind to record without a zone'); + throw new ValidationError('Cannot bind to record without a zone', record); } return { dnsName: this.record.domainName, diff --git a/packages/aws-cdk-lib/aws-route53/lib/geo-location.ts b/packages/aws-cdk-lib/aws-route53/lib/geo-location.ts index 8fee035dc8d45..adfcce1bae807 100644 --- a/packages/aws-cdk-lib/aws-route53/lib/geo-location.ts +++ b/packages/aws-cdk-lib/aws-route53/lib/geo-location.ts @@ -1,3 +1,5 @@ +import { UnscopedValidationError } from '../../core/lib/errors'; + /** * Routing based on geographical location. */ @@ -49,21 +51,21 @@ export class GeoLocation { private static validateCountry(country: string) { if (!GeoLocation.COUNTRY_REGEX.test(country)) { // eslint-disable-next-line max-len - throw new Error(`Invalid country format for country: ${country}, country should be two-letter and uppercase country ISO 3166-1-alpha-2 code`); + throw new UnscopedValidationError(`Invalid country format for country: ${country}, country should be two-letter and uppercase country ISO 3166-1-alpha-2 code`); } } private static validateCountryForSubdivision(country: string) { if (!GeoLocation.COUNTRY_FOR_SUBDIVISION_REGEX.test(country)) { // eslint-disable-next-line max-len - throw new Error(`Invalid country for subdivisions geolocation: ${country}, only UA (Ukraine) and US (United states) are supported`); + throw new UnscopedValidationError(`Invalid country for subdivisions geolocation: ${country}, only UA (Ukraine) and US (United states) are supported`); } } private static validateSubDivision(subDivision: string) { if (!GeoLocation.SUBDIVISION_REGEX.test(subDivision)) { // eslint-disable-next-line max-len - throw new Error(`Invalid subdivision format for subdivision: ${subDivision}, subdivision should be alphanumeric and between 1 and 3 characters`); + throw new UnscopedValidationError(`Invalid subdivision format for subdivision: ${subDivision}, subdivision should be alphanumeric and between 1 and 3 characters`); } } diff --git a/packages/aws-cdk-lib/aws-route53/lib/hosted-zone.ts b/packages/aws-cdk-lib/aws-route53/lib/hosted-zone.ts index 8005be5656dc8..9722fffd07da4 100644 --- a/packages/aws-cdk-lib/aws-route53/lib/hosted-zone.ts +++ b/packages/aws-cdk-lib/aws-route53/lib/hosted-zone.ts @@ -10,6 +10,7 @@ import * as iam from '../../aws-iam'; import * as kms from '../../aws-kms'; import * as cxschema from '../../cloud-assembly-schema'; import { ContextProvider, Duration, Lazy, Resource, Stack } from '../../core'; +import { ValidationError } from '../../core/lib/errors'; /** * Common properties to create a Route 53 hosted zone @@ -105,7 +106,7 @@ export class HostedZone extends Resource implements IHostedZone { class Import extends Resource implements IHostedZone { public readonly hostedZoneId = hostedZoneId; public get zoneName(): string { - throw new Error('Cannot reference `zoneName` when using `HostedZone.fromHostedZoneId()`. A construct consuming this hosted zone may be trying to reference its `zoneName`. If this is the case, use `fromHostedZoneAttributes()` or `fromLookup()` instead.'); + throw new ValidationError('Cannot reference `zoneName` when using `HostedZone.fromHostedZoneId()`. A construct consuming this hosted zone may be trying to reference its `zoneName`. If this is the case, use `fromHostedZoneAttributes()` or `fromLookup()` instead.', this); } public get hostedZoneArn(): string { return makeHostedZoneArn(this, this.hostedZoneId); @@ -152,7 +153,7 @@ export class HostedZone extends Resource implements IHostedZone { */ public static fromLookup(scope: Construct, id: string, query: HostedZoneProviderProps): IHostedZone { if (!query.domainName) { - throw new Error('Cannot use undefined value for attribute `domainName`'); + throw new ValidationError('Cannot use undefined value for attribute `domainName`', scope); } const DEFAULT_HOSTED_ZONE: HostedZoneContextResponse = { @@ -242,7 +243,7 @@ export class HostedZone extends Resource implements IHostedZone { */ public enableDnssec(options: ZoneSigningOptions): IKeySigningKey { if (this.keySigningKey) { - throw new Error('DNSSEC is already enabled for this hosted zone'); + throw new ValidationError('DNSSEC is already enabled for this hosted zone', this); } this.keySigningKey = new KeySigningKey(this, 'KeySigningKey', { hostedZone: this, @@ -323,7 +324,7 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone { public static fromPublicHostedZoneId(scope: Construct, id: string, publicHostedZoneId: string): IPublicHostedZone { class Import extends Resource implements IPublicHostedZone { public readonly hostedZoneId = publicHostedZoneId; - public get zoneName(): string { throw new Error('Cannot reference `zoneName` when using `PublicHostedZone.fromPublicHostedZoneId()`. A construct consuming this hosted zone may be trying to reference its `zoneName`. If this is the case, use `fromPublicHostedZoneAttributes()` instead'); } + public get zoneName(): string { throw new ValidationError('Cannot reference `zoneName` when using `PublicHostedZone.fromPublicHostedZoneId()`. A construct consuming this hosted zone may be trying to reference its `zoneName`. If this is the case, use `fromPublicHostedZoneAttributes()` instead', this); } public get hostedZoneArn(): string { return makeHostedZoneArn(this, this.hostedZoneId); } @@ -404,7 +405,7 @@ export class PublicHostedZone extends HostedZone implements IPublicHostedZone { } public addVpc(_vpc: ec2.IVpc) { - throw new Error('Cannot associate public hosted zones with a VPC'); + throw new ValidationError('Cannot associate public hosted zones with a VPC', this); } /** @@ -483,7 +484,7 @@ export class PrivateHostedZone extends HostedZone implements IPrivateHostedZone public static fromPrivateHostedZoneId(scope: Construct, id: string, privateHostedZoneId: string): IPrivateHostedZone { class Import extends Resource implements IPrivateHostedZone { public readonly hostedZoneId = privateHostedZoneId; - public get zoneName(): string { throw new Error('Cannot reference `zoneName` when using `PrivateHostedZone.fromPrivateHostedZoneId()`. A construct consuming this hosted zone may be trying to reference its `zoneName`'); } + public get zoneName(): string { throw new ValidationError('Cannot reference `zoneName` when using `PrivateHostedZone.fromPrivateHostedZoneId()`. A construct consuming this hosted zone may be trying to reference its `zoneName`', this); } public get hostedZoneArn(): string { return makeHostedZoneArn(this, this.hostedZoneId); } diff --git a/packages/aws-cdk-lib/aws-route53/lib/record-set.ts b/packages/aws-cdk-lib/aws-route53/lib/record-set.ts index 7c90833a95f6f..7780fb207a771 100644 --- a/packages/aws-cdk-lib/aws-route53/lib/record-set.ts +++ b/packages/aws-cdk-lib/aws-route53/lib/record-set.ts @@ -7,6 +7,7 @@ import { CfnRecordSet } from './route53.generated'; import { determineFullyQualifiedDomainName } from './util'; import * as iam from '../../aws-iam'; import { CustomResource, Duration, IResource, Names, RemovalPolicy, Resource, Token } from '../../core'; +import { ValidationError } from '../../core/lib/errors'; import { CrossAccountZoneDelegationProvider } from '../../custom-resource-handlers/dist/aws-route53/cross-account-zone-delegation-provider.generated'; import { DeleteExistingRecordSetProvider } from '../../custom-resource-handlers/dist/aws-route53/delete-existing-record-set-provider.generated'; @@ -340,16 +341,16 @@ export class RecordSet extends Resource implements IRecordSet { super(scope, id); if (props.weight && !Token.isUnresolved(props.weight) && (props.weight < 0 || props.weight > 255)) { - throw new Error(`weight must be between 0 and 255 inclusive, got: ${props.weight}`); + throw new ValidationError(`weight must be between 0 and 255 inclusive, got: ${props.weight}`, this); } if (props.setIdentifier && (props.setIdentifier.length < 1 || props.setIdentifier.length > 128)) { - throw new Error(`setIdentifier must be between 1 and 128 characters long, got: ${props.setIdentifier.length}`); + throw new ValidationError(`setIdentifier must be between 1 and 128 characters long, got: ${props.setIdentifier.length}`, this); } if (props.setIdentifier && props.weight === undefined && !props.geoLocation && !props.region && !props.multiValueAnswer) { - throw new Error('setIdentifier can only be specified for non-simple routing policies'); + throw new ValidationError('setIdentifier can only be specified for non-simple routing policies', this); } if (props.multiValueAnswer && props.target.aliasTarget) { - throw new Error('multiValueAnswer cannot be specified for alias record'); + throw new ValidationError('multiValueAnswer cannot be specified for alias record', this); } const nonSimpleRoutingPolicies = [ @@ -359,7 +360,7 @@ export class RecordSet extends Resource implements IRecordSet { props.multiValueAnswer, ].filter((variable) => variable !== undefined).length; if (nonSimpleRoutingPolicies > 1) { - throw new Error('Only one of region, weight, multiValueAnswer or geoLocation can be defined'); + throw new ValidationError('Only one of region, weight, multiValueAnswer or geoLocation can be defined', this); } this.geoLocation = props.geoLocation; @@ -546,9 +547,9 @@ class ARecordAsAliasTarget implements IAliasRecordTarget { constructor(private readonly aRrecordAttrs: ARecordAttrs) { } - public bind(_record: IRecordSet, _zone?: IHostedZone | undefined): AliasRecordTargetConfig { - if (!_zone) { - throw new Error('Cannot bind to record without a zone'); + public bind(record: IRecordSet, zone?: IHostedZone | undefined): AliasRecordTargetConfig { + if (!zone) { + throw new ValidationError('Cannot bind to record without a zone', record); } return { dnsName: this.aRrecordAttrs.targetDNS, diff --git a/packages/aws-cdk-lib/aws-route53/lib/vpc-endpoint-service-domain-name.ts b/packages/aws-cdk-lib/aws-route53/lib/vpc-endpoint-service-domain-name.ts index b3854f1570d99..4b54b5a383150 100644 --- a/packages/aws-cdk-lib/aws-route53/lib/vpc-endpoint-service-domain-name.ts +++ b/packages/aws-cdk-lib/aws-route53/lib/vpc-endpoint-service-domain-name.ts @@ -1,6 +1,7 @@ import { Construct } from 'constructs'; import { IVpcEndpointService } from '../../aws-ec2'; import { Fn, Names, Stack } from '../../core'; +import { ValidationError } from '../../core/lib/errors'; import { md5hash } from '../../core/lib/helpers-internal'; import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from '../../custom-resources'; import { IPublicHostedZone, TxtRecord } from '../lib'; @@ -80,8 +81,7 @@ export class VpcEndpointServiceDomainName extends Construct { const serviceUniqueId = Names.nodeUniqueId(props.endpointService.node); if (serviceUniqueId in VpcEndpointServiceDomainName.endpointServicesMap) { const endpoint = VpcEndpointServiceDomainName.endpointServicesMap[serviceUniqueId]; - throw new Error( - `Cannot create a VpcEndpointServiceDomainName for service ${serviceUniqueId}, another VpcEndpointServiceDomainName (${endpoint}) is already associated with it`); + throw new ValidationError(`Cannot create a VpcEndpointServiceDomainName for service ${serviceUniqueId}, another VpcEndpointServiceDomainName (${endpoint}) is already associated with it`, this); } }