Skip to content

Commit d12854a

Browse files
feat(codebuild): throw ValidationError instead of untyped Errors (#33700)
### Issue # (if applicable) Relates to #32569 ### Reason for this change untyped Errors are not recommended ### Description of changes ValidationErrors everywhere ### Describe any new or updated permissions being added None ### Description of how you validated changes Existing tests. Exemptions granted as this is 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 48ae4d0 commit d12854a

File tree

7 files changed

+43
-41
lines changed

7 files changed

+43
-41
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ const enableNoThrowDefaultErrorIn = [
2929
'aws-appsync',
3030
'aws-appmesh',
3131
'aws-autoscaling',
32-
'aws-autoscaling-common',
32+
'aws-autoscaling-common',
3333
'aws-backup',
3434
'aws-batch',
3535
'aws-chatbot',
3636
'aws-certificatemanager',
37+
'aws-codebuild',
3738
'aws-cognito',
3839
'aws-cloudfront',
3940
'aws-cloudfront-origins',

packages/aws-cdk-lib/aws-codebuild/lib/build-spec.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Construct } from 'constructs';
22
import * as yaml_cfn from './private/yaml-cfn';
33
import { Project } from './project';
44
import * as s3_assets from '../../aws-s3-assets';
5-
import { IResolveContext, Lazy, Stack } from '../../core';
5+
import { IResolveContext, Lazy, Stack, UnscopedValidationError } from '../../core';
66

77
/**
88
* BuildSpec for CodeBuild projects
@@ -66,7 +66,7 @@ class AssetBuildSpec extends BuildSpec {
6666

6767
public toBuildSpec(scope?: Project): string {
6868
if (!scope) {
69-
throw new Error('`AssetBuildSpec` requires a `scope` argument');
69+
throw new UnscopedValidationError('`AssetBuildSpec` requires a `scope` argument');
7070
}
7171

7272
// If the same AssetCode is used multiple times, retain only the first instantiation.
@@ -76,7 +76,7 @@ class AssetBuildSpec extends BuildSpec {
7676
...this.options,
7777
});
7878
} else if (Stack.of(this.asset) !== Stack.of(scope)) {
79-
throw new Error(`Asset is already associated with another stack '${Stack.of(this.asset).stackName}'. ` +
79+
throw new UnscopedValidationError(`Asset is already associated with another stack '${Stack.of(this.asset).stackName}'. ` +
8080
'Create a new BuildSpec instance for every stack.');
8181
}
8282

@@ -155,18 +155,18 @@ class YamlBuildSpec extends BuildSpec {
155155
*/
156156
export function mergeBuildSpecs(lhs: BuildSpec, rhs: BuildSpec): BuildSpec {
157157
if (!(lhs instanceof ObjectBuildSpec) || !(rhs instanceof ObjectBuildSpec)) {
158-
throw new Error('Can only merge buildspecs created using BuildSpec.fromObject()');
158+
throw new UnscopedValidationError('Can only merge buildspecs created using BuildSpec.fromObject()');
159159
}
160160

161161
if (lhs.spec.version === '0.1') {
162-
throw new Error('Cannot extend buildspec at version "0.1". Set the version to "0.2" or higher instead.');
162+
throw new UnscopedValidationError('Cannot extend buildspec at version "0.1". Set the version to "0.2" or higher instead.');
163163
}
164164
if (lhs.spec.artifacts && rhs.spec.artifacts) {
165165
// We decided to disallow merging of artifact specs, which is
166166
// actually impossible since we can't merge two buildspecs with a
167167
// single primary output into a buildspec with multiple outputs.
168168
// In case of multiple outputs they must have identifiers but we won't have that information.
169-
throw new Error('Only one build spec is allowed to specify artifacts.');
169+
throw new UnscopedValidationError('Only one build spec is allowed to specify artifacts.');
170170
}
171171

172172
const lhsSpec = JSON.parse(JSON.stringify(lhs.spec));

packages/aws-cdk-lib/aws-codebuild/lib/fleet.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Construct } from 'constructs';
22
import { CfnFleet } from './codebuild.generated';
33
import { ComputeType } from './compute-type';
44
import { EnvironmentType } from './environment-type';
5-
import { Arn, ArnFormat, IResource, Resource, Size, Token } from '../../core';
5+
import { Arn, ArnFormat, IResource, Resource, Size, Token, UnscopedValidationError, ValidationError } from '../../core';
66
import { addConstructMetadata } from '../../core/lib/metadata-resource';
77

88
/**
@@ -155,13 +155,13 @@ export class Fleet extends Resource implements IFleet {
155155
public readonly fleetArn = fleetArn;
156156

157157
public get computeType(): FleetComputeType {
158-
throw new Error('Cannot retrieve computeType property from an imported Fleet');
158+
throw new UnscopedValidationError('Cannot retrieve computeType property from an imported Fleet');
159159
}
160160
public get environmentType(): EnvironmentType {
161-
throw new Error('Cannot retrieve environmentType property from an imported Fleet');
161+
throw new UnscopedValidationError('Cannot retrieve environmentType property from an imported Fleet');
162162
}
163163
public get computeConfiguration(): ComputeConfiguration | undefined {
164-
throw new Error('Cannot retrieve computeConfiguration property from an imported Fleet');
164+
throw new UnscopedValidationError('Cannot retrieve computeConfiguration property from an imported Fleet');
165165
}
166166
}
167167

@@ -198,25 +198,25 @@ export class Fleet extends Resource implements IFleet {
198198

199199
if (props.fleetName && !Token.isUnresolved(props.fleetName)) {
200200
if (props.fleetName.length < 2) {
201-
throw new Error(`Fleet name can not be shorter than 2 characters but has ${props.fleetName.length} characters.`);
201+
throw new ValidationError(`Fleet name can not be shorter than 2 characters but has ${props.fleetName.length} characters.`, this);
202202
}
203203
if (props.fleetName.length > 128) {
204-
throw new Error(`Fleet name can not be longer than 128 characters but has ${props.fleetName.length} characters.`);
204+
throw new ValidationError(`Fleet name can not be longer than 128 characters but has ${props.fleetName.length} characters.`, this);
205205
}
206206
}
207207

208208
if ((props.baseCapacity ?? 1) < 1) {
209-
throw new Error('baseCapacity must be greater than or equal to 1');
209+
throw new ValidationError('baseCapacity must be greater than or equal to 1', this);
210210
}
211211

212212
if (
213213
props.computeType === FleetComputeType.ATTRIBUTE_BASED &&
214214
(!props.computeConfiguration || Object.keys(props.computeConfiguration).length === 0)
215215
) {
216-
throw new Error('At least one compute configuration criteria must be specified if computeType is "ATTRIBUTE_BASED"');
216+
throw new ValidationError('At least one compute configuration criteria must be specified if computeType is "ATTRIBUTE_BASED"', this);
217217
}
218218
if (props.computeConfiguration && props.computeType !== FleetComputeType.ATTRIBUTE_BASED) {
219-
throw new Error(`'computeConfiguration' can only be specified if 'computeType' is 'ATTRIBUTE_BASED', got: ${props.computeType}`);
219+
throw new ValidationError(`'computeConfiguration' can only be specified if 'computeType' is 'ATTRIBUTE_BASED', got: ${props.computeType}`, this);
220220
}
221221

222222
// Despite what the CloudFormation schema says, the numeric properties are not optional.
@@ -257,7 +257,7 @@ export class Fleet extends Resource implements IFleet {
257257

258258
private validatePositiveInteger(value: number, fieldName: string) {
259259
if (!Token.isUnresolved(value) && (value < 0 || !Number.isInteger(value))) {
260-
throw new Error(`${fieldName} must be a positive integer, got: ${value}`);
260+
throw new ValidationError(`${fieldName} must be a positive integer, got: ${value}`, this);
261261
}
262262
}
263263
}

packages/aws-cdk-lib/aws-codebuild/lib/linux-gpu-build-image.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export class LinuxGpuBuildImage implements IBindableBuildImage {
114114
const imageAccount = account ?? core.Lazy.string({
115115
produce: () => {
116116
if (this._imageAccount === undefined) {
117-
throw new Error('Make sure this \'LinuxGpuBuildImage\' is used in a CodeBuild Project construct');
117+
throw new core.UnscopedValidationError('Make sure this \'LinuxGpuBuildImage\' is used in a CodeBuild Project construct');
118118
}
119119
return this._imageAccount;
120120
},

packages/aws-cdk-lib/aws-codebuild/lib/project.ts

+15-15
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import * as iam from '../../aws-iam';
2828
import * as kms from '../../aws-kms';
2929
import * as s3 from '../../aws-s3';
3030
import * as secretsmanager from '../../aws-secretsmanager';
31-
import { ArnFormat, Aws, Duration, IResource, Lazy, Names, PhysicalName, Reference, Resource, SecretValue, Stack, Token, TokenComparison, Tokenization } from '../../core';
31+
import { ArnFormat, Aws, Duration, IResource, Lazy, Names, PhysicalName, Reference, Resource, SecretValue, Stack, Token, TokenComparison, Tokenization, UnscopedValidationError, ValidationError } from '../../core';
3232
import { addConstructMetadata, MethodMetadata } from '../../core/lib/metadata-resource';
3333

3434
const VPC_POLICY_SYM = Symbol.for('@aws-cdk/aws-codebuild.roleVpcPolicy');
@@ -275,7 +275,7 @@ abstract class ProjectBase extends Resource implements IProject {
275275
*/
276276
public get connections(): ec2.Connections {
277277
if (!this._connections) {
278-
throw new Error('Only VPC-associated Projects have security groups to manage. Supply the "vpc" parameter when creating the Project');
278+
throw new ValidationError('Only VPC-associated Projects have security groups to manage. Supply the "vpc" parameter when creating the Project', this);
279279
}
280280
return this._connections;
281281
}
@@ -887,7 +887,7 @@ export class Project extends ProjectBase {
887887
const fragments = Tokenization.reverseString(cfnEnvVariable.value);
888888
for (const token of fragments.tokens) {
889889
if (token instanceof SecretValue) {
890-
throw new Error(`Plaintext environment variable '${name}' contains a secret value! ` +
890+
throw new UnscopedValidationError(`Plaintext environment variable '${name}' contains a secret value! ` +
891891
'This means the value of this variable will be visible in plain text in the AWS Console. ' +
892892
"Please consider using CodeBuild's SecretsManager environment variables feature instead. " +
893893
"If you'd like to continue with having this secret in the plaintext environment variables, " +
@@ -921,7 +921,7 @@ export class Project extends ProjectBase {
921921
if (envVariableValue.startsWith('arn:')) {
922922
const parsedArn = stack.splitArn(envVariableValue, ArnFormat.COLON_RESOURCE_NAME);
923923
if (!parsedArn.resourceName) {
924-
throw new Error('SecretManager ARN is missing the name of the secret: ' + envVariableValue);
924+
throw new UnscopedValidationError('SecretManager ARN is missing the name of the secret: ' + envVariableValue);
925925
}
926926

927927
// the value of the property can be a complex string, separated by ':';
@@ -1077,7 +1077,7 @@ export class Project extends ProjectBase {
10771077
this.source = props.source || new NoSource();
10781078
const sourceConfig = this.source.bind(this, this);
10791079
if (props.badge && !this.source.badgeSupported) {
1080-
throw new Error(`Badge is not supported for source type ${this.source.type}`);
1080+
throw new ValidationError(`Badge is not supported for source type ${this.source.type}`, this);
10811081
}
10821082

10831083
const artifacts = props.artifacts
@@ -1096,7 +1096,7 @@ export class Project extends ProjectBase {
10961096
const environmentVariables = props.environmentVariables || {};
10971097
const buildSpec = props.buildSpec;
10981098
if (this.source.type === NO_SOURCE_TYPE && (buildSpec === undefined || !buildSpec.isImmediate)) {
1099-
throw new Error("If the Project's source is NoSource, you need to provide a concrete buildSpec");
1099+
throw new ValidationError("If the Project's source is NoSource, you need to provide a concrete buildSpec", this);
11001100
}
11011101

11021102
this._secondarySources = [];
@@ -1119,7 +1119,7 @@ export class Project extends ProjectBase {
11191119

11201120
if (!Token.isUnresolved(props.autoRetryLimit) && (props.autoRetryLimit !== undefined)) {
11211121
if (props.autoRetryLimit < 0 || props.autoRetryLimit > 10) {
1122-
throw new Error(`autoRetryLimit must be a value between 0 and 10, got ${props.autoRetryLimit}.`);
1122+
throw new ValidationError(`autoRetryLimit must be a value between 0 and 10, got ${props.autoRetryLimit}.`, this);
11231123
}
11241124
}
11251125

@@ -1252,7 +1252,7 @@ export class Project extends ProjectBase {
12521252
@MethodMetadata()
12531253
public addSecondarySource(secondarySource: ISource): void {
12541254
if (!secondarySource.identifier) {
1255-
throw new Error('The identifier attribute is mandatory for secondary sources');
1255+
throw new ValidationError('The identifier attribute is mandatory for secondary sources', this);
12561256
}
12571257
const secondarySourceConfig = secondarySource.bind(this, this);
12581258
this._secondarySources.push(secondarySourceConfig.sourceProperty);
@@ -1284,7 +1284,7 @@ export class Project extends ProjectBase {
12841284
@MethodMetadata()
12851285
public addSecondaryArtifact(secondaryArtifact: IArtifacts): void {
12861286
if (!secondaryArtifact.identifier) {
1287-
throw new Error('The identifier attribute is mandatory for secondary artifacts');
1287+
throw new ValidationError('The identifier attribute is mandatory for secondary artifacts', this);
12881288
}
12891289
this._secondaryArtifacts.push(secondaryArtifact.bind(this, this).artifactsProperty);
12901290
}
@@ -1372,7 +1372,7 @@ export class Project extends ProjectBase {
13721372
errors.push(...this.validateLambdaBuildImage(this.buildImage, props));
13731373

13741374
if (errors.length > 0) {
1375-
throw new Error('Invalid CodeBuild environment: ' + errors.join('\n'));
1375+
throw new ValidationError('Invalid CodeBuild environment: ' + errors.join('\n'), this);
13761376
}
13771377

13781378
const imagePullPrincipalType = this.isLambdaBuildImage(this.buildImage) ? undefined :
@@ -1449,7 +1449,7 @@ export class Project extends ProjectBase {
14491449

14501450
// If the fleetArn is resolved, the fleet is imported and we cannot validate the environment type
14511451
if (Token.isUnresolved(fleet.fleetArn) && this.buildImage.type !== fleet.environmentType) {
1452-
throw new Error(`The environment type of the fleet (${fleet.environmentType}) must match the environment type of the build image (${this.buildImage.type})`);
1452+
throw new ValidationError(`The environment type of the fleet (${fleet.environmentType}) must match the environment type of the build image (${this.buildImage.type})`, this);
14531453
}
14541454

14551455
return { fleetArn: fleet.fleetArn };
@@ -1463,13 +1463,13 @@ export class Project extends ProjectBase {
14631463
*/
14641464
private configureVpc(props: ProjectProps): CfnProject.VpcConfigProperty | undefined {
14651465
if ((props.securityGroups || props.allowAllOutbound !== undefined) && !props.vpc) {
1466-
throw new Error('Cannot configure \'securityGroup\' or \'allowAllOutbound\' without configuring a VPC');
1466+
throw new ValidationError('Cannot configure \'securityGroup\' or \'allowAllOutbound\' without configuring a VPC', this);
14671467
}
14681468

14691469
if (!props.vpc) { return undefined; }
14701470

14711471
if ((props.securityGroups && props.securityGroups.length > 0) && props.allowAllOutbound !== undefined) {
1472-
throw new Error('Configure \'allowAllOutbound\' directly on the supplied SecurityGroup.');
1472+
throw new ValidationError('Configure \'allowAllOutbound\' directly on the supplied SecurityGroup.', this);
14731473
}
14741474

14751475
let securityGroups: ec2.ISecurityGroup[];
@@ -1515,7 +1515,7 @@ export class Project extends ProjectBase {
15151515
const status = (cloudWatchLogs.enabled ?? true) ? 'ENABLED' : 'DISABLED';
15161516

15171517
if (status === 'ENABLED' && !(cloudWatchLogs.logGroup)) {
1518-
throw new Error('Specifying a LogGroup is required if CloudWatch logging for CodeBuild is enabled');
1518+
throw new ValidationError('Specifying a LogGroup is required if CloudWatch logging for CodeBuild is enabled', this);
15191519
}
15201520
cloudWatchLogs.logGroup?.grantWrite(this);
15211521

@@ -1591,7 +1591,7 @@ export class Project extends ProjectBase {
15911591
if ((sourceType === CODEPIPELINE_SOURCE_ARTIFACTS_TYPE ||
15921592
artifactsType === CODEPIPELINE_SOURCE_ARTIFACTS_TYPE) &&
15931593
(sourceType !== artifactsType)) {
1594-
throw new Error('Both source and artifacts must be set to CodePipeline');
1594+
throw new ValidationError('Both source and artifacts must be set to CodePipeline', this);
15951595
}
15961596
}
15971597

packages/aws-cdk-lib/aws-codebuild/lib/report-group.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ export class ReportGroup extends ReportGroupBase {
190190
this.exportBucket = props.exportBucket;
191191

192192
if (props.deleteReports && props.removalPolicy !== cdk.RemovalPolicy.DESTROY) {
193-
throw new Error('Cannot use \'deleteReports\' property on a report group without setting removal policy to \'DESTROY\'.');
193+
throw new cdk.ValidationError('Cannot use \'deleteReports\' property on a report group without setting removal policy to \'DESTROY\'.', this);
194194
}
195195
}
196196
}

packages/aws-cdk-lib/aws-codebuild/lib/source.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import * as codecommit from '../../aws-codecommit';
1212
import * as iam from '../../aws-iam';
1313
import * as s3 from '../../aws-s3';
14+
import { UnscopedValidationError } from '../../core';
1415

1516
/**
1617
* The type returned from `ISource#bind`.
@@ -246,7 +247,7 @@ export class FilterGroup {
246247

247248
private constructor(actions: Set<EventAction>, filters: CfnProject.WebhookFilterProperty[]) {
248249
if (actions.size === 0) {
249-
throw new Error('A filter group must contain at least one event action');
250+
throw new UnscopedValidationError('A filter group must contain at least one event action');
250251
}
251252
this.actions = actions;
252253
this.filters = filters;
@@ -491,7 +492,7 @@ export class FilterGroup {
491492

492493
private addBaseRefFilter(refName: string, include: boolean) {
493494
if (this.actions.has(EventAction.PUSH)) {
494-
throw new Error('A base reference condition cannot be added if a Group contains a PUSH event action');
495+
throw new UnscopedValidationError('A base reference condition cannot be added if a Group contains a PUSH event action');
495496
}
496497
return this.addFilter(WebhookFilterTypes.BASE_REF, refName, include);
497498
}
@@ -588,11 +589,11 @@ abstract class ThirdPartyGitSource extends GitSource {
588589
const webhook = this.webhook ?? (anyFilterGroupsProvided ? true : undefined);
589590

590591
if (!webhook && anyFilterGroupsProvided) {
591-
throw new Error('`webhookFilters` cannot be used when `webhook` is `false`');
592+
throw new UnscopedValidationError('`webhookFilters` cannot be used when `webhook` is `false`');
592593
}
593594

594595
if (!webhook && this.webhookTriggersBatchBuild) {
595-
throw new Error('`webhookTriggersBatchBuild` cannot be used when `webhook` is `false`');
596+
throw new UnscopedValidationError('`webhookTriggersBatchBuild` cannot be used when `webhook` is `false`');
596597
}
597598

598599
const superConfig = super.bind(_scope, project);
@@ -829,11 +830,11 @@ class GitHubEnterpriseSource extends CommonGithubSource {
829830

830831
public bind(_scope: Construct, _project: IProject): SourceConfig {
831832
if (this.hasCommitMessageFilterAndPrEvent()) {
832-
throw new Error('COMMIT_MESSAGE filters cannot be used with GitHub Enterprise Server pull request events');
833+
throw new UnscopedValidationError('COMMIT_MESSAGE filters cannot be used with GitHub Enterprise Server pull request events');
833834
}
834835

835836
if (this.hasFilePathFilterAndPrEvent()) {
836-
throw new Error('FILE_PATH filters cannot be used with GitHub Enterprise Server pull request events');
837+
throw new UnscopedValidationError('FILE_PATH filters cannot be used with GitHub Enterprise Server pull request events');
837838
}
838839

839840
const superConfig = super.bind(_scope, _project);
@@ -915,7 +916,7 @@ class BitBucketSource extends ThirdPartyGitSource {
915916
public bind(_scope: Construct, _project: IProject): SourceConfig {
916917
// BitBucket sources don't support the PULL_REQUEST_REOPENED event action
917918
if (this.anyWebhookFilterContainsPrReopenedEventAction()) {
918-
throw new Error('BitBucket sources do not support the PULL_REQUEST_REOPENED webhook event action');
919+
throw new UnscopedValidationError('BitBucket sources do not support the PULL_REQUEST_REOPENED webhook event action');
919920
}
920921

921922
const superConfig = super.bind(_scope, _project);

0 commit comments

Comments
 (0)