diff --git a/packages/aws-cdk-lib/.eslintrc.js b/packages/aws-cdk-lib/.eslintrc.js index 327bffecd966a..7d6163f1979af 100644 --- a/packages/aws-cdk-lib/.eslintrc.js +++ b/packages/aws-cdk-lib/.eslintrc.js @@ -24,6 +24,16 @@ const enableNoThrowDefaultErrorIn = [ 'aws-elasticloadbalancingv2-targets', 'aws-lambda', 'aws-rds', + 'aws-s3', + 'aws-sns', + 'aws-sqs', + 'aws-ssm', + 'aws-ssmcontacts', + 'aws-ssmincidents', + 'aws-ssmquicksetup', + 'aws-apigatewayv2', + 'aws-apigatewayv2-authorizers', + 'aws-synthetics', 'aws-route53', 'aws-route53-patterns', 'aws-route53-targets', diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/common/api-mapping.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/common/api-mapping.ts index 05d9cca6c4bc8..cc0c17cea76ff 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/common/api-mapping.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/common/api-mapping.ts @@ -4,6 +4,7 @@ import { IDomainName } from './domain-name'; import { IStage } from './stage'; import { CfnApiMapping, CfnApiMappingProps } from '.././index'; import { IResource, Resource } from '../../../core'; +import { ValidationError } from '../../../core/lib/errors'; /** * Represents an ApiGatewayV2 ApiMapping resource @@ -95,11 +96,11 @@ export class ApiMapping extends Resource implements IApiMapping { // So casting to 'any' let stage = props.stage ?? (props.api as any).defaultStage; if (!stage) { - throw new Error('stage property must be specified'); + throw new ValidationError('stage property must be specified', scope); } if (props.apiMappingKey === '') { - throw new Error('empty string for api mapping key not allowed'); + throw new ValidationError('empty string for api mapping key not allowed', scope); } const apiMappingProps: CfnApiMappingProps = { diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/common/base.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/common/base.ts index 990a79ed5c75b..975eb1754c287 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/common/base.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/common/base.ts @@ -3,6 +3,7 @@ import { ApiMapping } from './api-mapping'; import { DomainMappingOptions, IStage } from './stage'; import * as cloudwatch from '../../../aws-cloudwatch'; import { Resource } from '../../../core'; +import { UnscopedValidationError } from '../../../core/lib/errors'; /** * Base class representing an API @@ -46,7 +47,7 @@ export abstract class StageBase extends Resource implements IStage { */ protected _addDomainMapping(domainMapping: DomainMappingOptions) { if (this._apiMapping) { - throw new Error('Only one ApiMapping allowed per Stage'); + throw new UnscopedValidationError('Only one ApiMapping allowed per Stage'); } this._apiMapping = new ApiMapping(this, `${domainMapping.domainName}${domainMapping.mappingKey}`, { api: this.baseApi, diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/common/domain-name.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/common/domain-name.ts index bdc0bc7d0d549..866da51ca02b1 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/common/domain-name.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/common/domain-name.ts @@ -3,6 +3,7 @@ import { CfnDomainName, CfnDomainNameProps } from '.././index'; import { ICertificate } from '../../../aws-certificatemanager'; import { IBucket } from '../../../aws-s3'; import { IResource, Lazy, Resource, Token } from '../../../core'; +import { ValidationError } from '../../../core/lib/errors'; /** * The minimum version of the SSL protocol that you want API Gateway to use for HTTPS connections. @@ -172,12 +173,12 @@ export class DomainName extends Resource implements IDomainName { super(scope, id); if (props.domainName === '') { - throw new Error('empty string for domainName not allowed'); + throw new ValidationError('empty string for domainName not allowed', scope); } // validation for ownership certificate if (props.ownershipCertificate && !props.mtls) { - throw new Error('ownership certificate can only be used with mtls domains'); + throw new ValidationError('ownership certificate can only be used with mtls domains', scope); } const mtlsConfig = this.configureMTLS(props.mtls); @@ -225,7 +226,7 @@ export class DomainName extends Resource implements IDomainName { private validateEndpointType(endpointType: string | undefined) : void { for (let config of this.domainNameConfigurations) { if (endpointType && endpointType == config.endpointType) { - throw new Error(`an endpoint with type ${endpointType} already exists`); + throw new ValidationError(`an endpoint with type ${endpointType} already exists`, this); } } } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/api.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/api.ts index e447aa0179a50..c30c04217a105 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/api.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/api.ts @@ -7,10 +7,10 @@ import { VpcLink, VpcLinkProps } from './vpc-link'; import { CfnApi, CfnApiProps } from '.././index'; import { Metric, MetricOptions } from '../../../aws-cloudwatch'; import { ArnFormat, Duration, Stack, Token } from '../../../core'; +import { ValidationError } from '../../../core/lib/errors'; import { IApi } from '../common/api'; import { ApiBase } from '../common/base'; import { DomainMappingOptions } from '../common/stage'; - /** * Represents an HTTP API */ @@ -314,7 +314,7 @@ abstract class HttpApiBase extends ApiBase implements IHttpApi { // note that th public arnForExecuteApi(method?: string, path?: string, stage?: string): string { if (path && !Token.isUnresolved(path) && !path.startsWith('/')) { - throw new Error(`Path must start with '/': ${path}`); + throw new ValidationError(`Path must start with '/': ${path}`, this); } if (method && method.toUpperCase() === 'ANY') { @@ -363,7 +363,7 @@ export class HttpApi extends HttpApiBase { public get apiEndpoint(): string { if (!this._apiEndpoint) { - throw new Error('apiEndpoint is not configured on the imported HttpApi.'); + throw new ValidationError('apiEndpoint is not configured on the imported HttpApi.', scope); } return this._apiEndpoint; } @@ -416,7 +416,7 @@ export class HttpApi extends HttpApiBase { if (props?.corsPreflight) { const cors = props.corsPreflight; if (cors.allowOrigins && cors.allowOrigins.includes('*') && cors.allowCredentials) { - throw new Error("CORS preflight - allowCredentials is not supported when allowOrigin is '*'"); + throw new ValidationError("CORS preflight - allowCredentials is not supported when allowOrigin is '*'", scope); } const { allowCredentials, @@ -476,8 +476,7 @@ export class HttpApi extends HttpApiBase { } if (props?.createDefaultStage === false && props.defaultDomainMapping) { - throw new Error('defaultDomainMapping not supported with createDefaultStage disabled', - ); + throw new ValidationError('defaultDomainMapping not supported with createDefaultStage disabled', scope); } } @@ -486,7 +485,7 @@ export class HttpApi extends HttpApiBase { */ public get apiEndpoint(): string { if (this.disableExecuteApiEndpoint) { - throw new Error('apiEndpoint is not accessible when disableExecuteApiEndpoint is set to true.'); + throw new ValidationError('apiEndpoint is not accessible when disableExecuteApiEndpoint is set to true.', this); } return this._apiEndpoint; } diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/authorizer.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/authorizer.ts index 0221a11c1dfea..a3aa4f015f583 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/authorizer.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/authorizer.ts @@ -3,7 +3,7 @@ import { IHttpApi } from './api'; import { IHttpRoute } from './route'; import { CfnAuthorizer } from '.././index'; import { Duration, Resource } from '../../../core'; - +import { ValidationError } from '../../../core/lib/errors'; import { IAuthorizer } from '../common'; /** @@ -161,11 +161,11 @@ export class HttpAuthorizer extends Resource implements IHttpAuthorizer { let authorizerPayloadFormatVersion = props.payloadFormatVersion; if (props.type === HttpAuthorizerType.JWT && (!props.jwtAudience || props.jwtAudience.length === 0 || !props.jwtIssuer)) { - throw new Error('jwtAudience and jwtIssuer are mandatory for JWT authorizers'); + throw new ValidationError('jwtAudience and jwtIssuer are mandatory for JWT authorizers', scope); } if (props.type === HttpAuthorizerType.LAMBDA && !props.authorizerUri) { - throw new Error('authorizerUri is mandatory for Lambda authorizers'); + throw new ValidationError('authorizerUri is mandatory for Lambda authorizers', scope); } /** diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/integration.ts index c7b592c6dd8ca..f89954f2f4c6c 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/integration.ts @@ -4,6 +4,7 @@ import { HttpMethod, IHttpRoute } from './route'; import { CfnIntegration } from '.././index'; import { IRole } from '../../../aws-iam'; import { Aws, Duration, Resource } from '../../../core'; +import { ValidationError } from '../../../core/lib/errors'; import { IIntegration } from '../common'; import { ParameterMapping } from '../parameter-mapping'; @@ -254,11 +255,11 @@ export class HttpIntegration extends Resource implements IHttpIntegration { super(scope, id); if (!props.integrationSubtype && !props.integrationUri) { - throw new Error('Either `integrationSubtype` or `integrationUri` must be specified.'); + throw new ValidationError('Either `integrationSubtype` or `integrationUri` must be specified.', scope); } if (props.timeout && !props.timeout.isUnresolved() && (props.timeout.toMilliseconds() < 50 || props.timeout.toMilliseconds() > 29000)) { - throw new Error('Integration timeout must be between 50 milliseconds and 29 seconds.'); + throw new ValidationError('Integration timeout must be between 50 milliseconds and 29 seconds.', scope); } const integ = new CfnIntegration(this, 'Resource', { @@ -321,7 +322,7 @@ export abstract class HttpRouteIntegration { */ public _bindToRoute(options: HttpRouteIntegrationBindOptions): { readonly integrationId: string } { if (this.integration && this.integration.httpApi.node.addr !== options.route.httpApi.node.addr) { - throw new Error('A single integration cannot be associated with multiple APIs.'); + throw new ValidationError('A single integration cannot be associated with multiple APIs.', options.scope); } if (!this.integration) { diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/route.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/route.ts index 0c82e6597f1e3..941f72efd06d2 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/route.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/route.ts @@ -5,6 +5,7 @@ import { HttpRouteIntegration } from './integration'; import { CfnRoute, CfnRouteProps } from '.././index'; import * as iam from '../../../aws-iam'; import { Aws, Resource } from '../../../core'; +import { UnscopedValidationError, ValidationError } from '../../../core/lib/errors'; import { IRoute } from '../common'; /** @@ -84,7 +85,7 @@ export class HttpRouteKey { */ public static with(path: string, method?: HttpMethod) { if (path !== '/' && (!path.startsWith('/') || path.endsWith('/'))) { - throw new Error('A route path must always start with a "/" and not end with a "/"'); + throw new UnscopedValidationError('A route path must always start with a "/" and not end with a "/"'); } return new HttpRouteKey(method, path); } @@ -200,7 +201,7 @@ export class HttpRoute extends Resource implements IHttpRoute { }); if (this.authBindResult && !(this.authBindResult.authorizationType in HttpRouteAuthorizationType)) { - throw new Error(`authorizationType should either be AWS_IAM, JWT, CUSTOM, or NONE but was '${this.authBindResult.authorizationType}'`); + throw new ValidationError(`authorizationType should either be AWS_IAM, JWT, CUSTOM, or NONE but was '${this.authBindResult.authorizationType}'`, scope); } let authorizationScopes = this.authBindResult?.authorizationScopes; @@ -236,7 +237,7 @@ export class HttpRoute extends Resource implements IHttpRoute { // When the user has provided a path with path variables, we replace the // path variable and all that follows with a wildcard. if (path.length > 1000) { - throw new Error(`Path is too long: ${path}`); + throw new ValidationError(`Path is too long: ${path}`, this); }; const iamPath = path.replace(/\{.*?\}.*/, '*'); @@ -245,12 +246,12 @@ export class HttpRoute extends Resource implements IHttpRoute { public grantInvoke(grantee: iam.IGrantable, options: GrantInvokeOptions = {}): iam.Grant { if (!this.authBindResult || this.authBindResult.authorizationType !== HttpRouteAuthorizationType.AWS_IAM) { - throw new Error('To use grantInvoke, you must use IAM authorization'); + throw new ValidationError('To use grantInvoke, you must use IAM authorization', this); } const httpMethods = Array.from(new Set(options.httpMethods ?? [this.method])); if (this.method !== HttpMethod.ANY && httpMethods.some(method => method !== this.method)) { - throw new Error('This route does not support granting invoke for all requested http methods'); + throw new ValidationError('This route does not support granting invoke for all requested http methods', this); } const resourceArns = httpMethods.map(httpMethod => { diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/stage.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/stage.ts index 0ec38b893af56..14cdb9ee7aa9b 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/stage.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/http/stage.ts @@ -3,6 +3,7 @@ import { IHttpApi } from './api'; import { CfnStage } from '.././index'; import { Metric, MetricOptions } from '../../../aws-cloudwatch'; import { Stack } from '../../../core'; +import { ValidationError } from '../../../core/lib/errors'; import { StageOptions, IStage, StageAttributes } from '../common'; import { IApi } from '../common/api'; import { StageBase } from '../common/base'; @@ -144,11 +145,11 @@ export class HttpStage extends HttpStageBase { public readonly api = attrs.api; get url(): string { - throw new Error('url is not available for imported stages.'); + throw new ValidationError('url is not available for imported stages.', scope); } get domainUrl(): string { - throw new Error('domainUrl is not available for imported stages.'); + throw new ValidationError('domainUrl is not available for imported stages.', scope); } } return new Import(scope, id); @@ -194,7 +195,7 @@ export class HttpStage extends HttpStageBase { public get domainUrl(): string { if (!this._apiMapping) { - throw new Error('domainUrl is not available when no API mapping is associated with the Stage'); + throw new ValidationError('domainUrl is not available when no API mapping is associated with the Stage', this); } return `https://${this._apiMapping.domainName.name}/${this._apiMapping.mappingKey ?? ''}`; diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/api.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/api.ts index 5a8f0a90e13b3..5a07db39a2c22 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/api.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/api.ts @@ -3,6 +3,7 @@ import { WebSocketRoute, WebSocketRouteOptions } from './route'; import { CfnApi } from '.././index'; import { Grant, IGrantable } from '../../../aws-iam'; import { ArnFormat, Stack, Token } from '../../../core'; +import { UnscopedValidationError, ValidationError } from '../../../core/lib/errors'; import { IApi } from '../common/api'; import { ApiBase } from '../common/base'; @@ -116,7 +117,7 @@ export class WebSocketApi extends ApiBase implements IWebSocketApi { public get apiEndpoint(): string { if (!this._apiEndpoint) { - throw new Error('apiEndpoint is not configured on the imported WebSocketApi.'); + throw new ValidationError('apiEndpoint is not configured on the imported WebSocketApi.', scope); } return this._apiEndpoint; } @@ -200,7 +201,7 @@ export class WebSocketApi extends ApiBase implements IWebSocketApi { */ public arnForExecuteApi(method?: string, path?: string, stage?: string): string { if (path && !Token.isUnresolved(path) && !path.startsWith('/')) { - throw new Error(`Path must start with '/': ${path}`); + throw new UnscopedValidationError(`Path must start with '/': ${path}`); } if (method && method.toUpperCase() === 'ANY') { diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/authorizer.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/authorizer.ts index 52fc6f6ff0cab..9ed1a8502ce7c 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/authorizer.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/authorizer.ts @@ -3,7 +3,7 @@ import { IWebSocketApi } from './api'; import { IWebSocketRoute } from './route'; import { CfnAuthorizer } from '.././index'; import { Resource } from '../../../core'; - +import { ValidationError } from '../../../core/lib/errors'; import { IAuthorizer } from '../common'; /** @@ -106,7 +106,7 @@ export class WebSocketAuthorizer extends Resource implements IWebSocketAuthorize super(scope, id); if (props.type === WebSocketAuthorizerType.LAMBDA && !props.authorizerUri) { - throw new Error('authorizerUri is mandatory for Lambda authorizers'); + throw new ValidationError('authorizerUri is mandatory for Lambda authorizers', scope); } const resource = new CfnAuthorizer(this, 'Resource', { diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts index ab2ed9d55817c..c7500e5198ccf 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/integration.ts @@ -4,6 +4,7 @@ import { IWebSocketRoute } from './route'; import { CfnIntegration } from '.././index'; import { IRole } from '../../../aws-iam'; import { Duration, Resource } from '../../../core'; +import { ValidationError } from '../../../core/lib/errors'; import { IIntegration } from '../common'; /** @@ -172,7 +173,7 @@ export class WebSocketIntegration extends Resource implements IWebSocketIntegrat super(scope, id); if (props.timeout && !props.timeout.isUnresolved() && (props.timeout.toMilliseconds() < 50 || props.timeout.toMilliseconds() > 29000)) { - throw new Error('Integration timeout must be between 50 milliseconds and 29 seconds.'); + throw new ValidationError('Integration timeout must be between 50 milliseconds and 29 seconds.', scope); } const integ = new CfnIntegration(this, 'Resource', { @@ -228,7 +229,7 @@ export abstract class WebSocketRouteIntegration { */ public _bindToRoute(options: WebSocketRouteIntegrationBindOptions): { readonly integrationId: string } { if (this.integration && this.integration.webSocketApi.node.addr !== options.route.webSocketApi.node.addr) { - throw new Error('A single integration cannot be associated with multiple APIs.'); + throw new ValidationError('A single integration cannot be associated with multiple APIs.', options.scope); } if (!this.integration) { diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts index 0114e13c50e0d..cf26704ccd29a 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/route.ts @@ -4,6 +4,7 @@ import { IWebSocketRouteAuthorizer, WebSocketNoneAuthorizer } from './authorizer import { WebSocketRouteIntegration } from './integration'; import { CfnRoute, CfnRouteResponse } from '.././index'; import { Resource } from '../../../core'; +import { ValidationError } from '../../../core/lib/errors'; import { IRoute } from '../common'; /** @@ -85,7 +86,7 @@ export class WebSocketRoute extends Resource implements IWebSocketRoute { super(scope, id); if (props.routeKey != '$connect' && props.authorizer) { - throw new Error('You can only set a WebSocket authorizer to a $connect route.'); + throw new ValidationError('You can only set a WebSocket authorizer to a $connect route.', scope); } this.webSocketApi = props.webSocketApi; diff --git a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/stage.ts b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/stage.ts index 6332b7532fd17..76f84ad4d13e4 100644 --- a/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/stage.ts +++ b/packages/aws-cdk-lib/aws-apigatewayv2/lib/websocket/stage.ts @@ -3,6 +3,7 @@ import { IWebSocketApi } from './api'; import { CfnStage } from '.././index'; import { Grant, IGrantable } from '../../../aws-iam'; import { Stack } from '../../../core'; +import { ValidationError } from '../../../core/lib/errors'; import { StageOptions, IApi, IStage, StageAttributes } from '../common'; import { StageBase } from '../common/base'; @@ -64,11 +65,11 @@ export class WebSocketStage extends StageBase implements IWebSocketStage { public readonly api = attrs.api; get url(): string { - throw new Error('url is not available for imported stages.'); + throw new ValidationError('url is not available for imported stages.', scope); } get callbackUrl(): string { - throw new Error('callback url is not available for imported stages.'); + throw new ValidationError('callback url is not available for imported stages.', scope); } } return new Import(scope, id);