Skip to content

Commit e4703c1

Browse files
authored
feat(synthetics): throw ValidationError instead of untyped errors (#33079)
### Issue `aws-synthetics` 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 704ff0b commit e4703c1

File tree

4 files changed

+30
-26
lines changed

4 files changed

+30
-26
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const enableNoThrowDefaultErrorIn = [
2525
'aws-ssmcontacts',
2626
'aws-ssmincidents',
2727
'aws-ssmquicksetup',
28+
'aws-synthetics',
2829
];
2930
baseConfig.overrides.push({
3031
files: enableNoThrowDefaultErrorIn.map(m => `./${m}/lib/**`),

packages/aws-cdk-lib/aws-synthetics/lib/canary.ts

+16-15
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as iam from '../../aws-iam';
1111
import * as kms from '../../aws-kms';
1212
import * as s3 from '../../aws-s3';
1313
import * as cdk from '../../core';
14+
import { UnscopedValidationError, ValidationError } from '../../core/lib/errors';
1415
import { AutoDeleteUnderlyingResourcesProvider } from '../../custom-resource-handlers/dist/aws-synthetics/auto-delete-underlying-resources-provider.generated';
1516

1617
const AUTO_DELETE_UNDERLYING_RESOURCES_RESOURCE_TYPE = 'Custom::SyntheticsAutoDeleteUnderlyingResources';
@@ -427,7 +428,7 @@ export class Canary extends cdk.Resource implements ec2.IConnectable {
427428
public get connections(): ec2.Connections {
428429
if (!this._connections) {
429430
// eslint-disable-next-line max-len
430-
throw new Error('Only VPC-associated Canaries have security groups to manage. Supply the "vpc" parameter when creating the Canary.');
431+
throw new ValidationError('Only VPC-associated Canaries have security groups to manage. Supply the "vpc" parameter when creating the Canary.', this);
431432
}
432433
return this._connections;
433434
}
@@ -568,15 +569,15 @@ export class Canary extends cdk.Resource implements ec2.IConnectable {
568569
];
569570
if (oldRuntimes.includes(runtime)) {
570571
if (!handler.match(/^[0-9A-Za-z_\\-]+\.handler*$/)) {
571-
throw new Error(`Canary Handler must be specified as \'fileName.handler\' for legacy runtimes, received ${handler}`);
572+
throw new ValidationError(`Canary Handler must be specified as \'fileName.handler\' for legacy runtimes, received ${handler}`, this);
572573
}
573574
} else {
574575
if (!handler.match(/^([0-9a-zA-Z_-]+\/)*[0-9A-Za-z_\\-]+\.[A-Za-z_][A-Za-z0-9_]*$/)) {
575-
throw new Error(`Canary Handler must be specified either as \'fileName.handler\', \'fileName.functionName\', or \'folder/fileName.functionName\', received ${handler}`);
576+
throw new ValidationError(`Canary Handler must be specified either as \'fileName.handler\', \'fileName.functionName\', or \'folder/fileName.functionName\', received ${handler}`, this);
576577
}
577578
}
578579
if (handler.length < 1 || handler.length > 128) {
579-
throw new Error(`Canary Handler length must be between 1 and 128, received ${handler.length}`);
580+
throw new ValidationError(`Canary Handler length must be between 1 and 128, received ${handler.length}`, this);
580581
}
581582
}
582583

@@ -596,30 +597,30 @@ export class Canary extends cdk.Resource implements ec2.IConnectable {
596597
(!cdk.Token.isUnresolved(props.runtime.name) && props.runtime.name.includes('playwright'))
597598
)
598599
) {
599-
throw new Error(`You can only enable active tracing for canaries that use canary runtime version 'syn-nodejs-2.0' or later and are not using the Playwright runtime, got ${props.runtime.name}.`);
600+
throw new ValidationError(`You can only enable active tracing for canaries that use canary runtime version 'syn-nodejs-2.0' or later and are not using the Playwright runtime, got ${props.runtime.name}.`, this);
600601
}
601602

602603
let memoryInMb: number | undefined;
603604
if (!cdk.Token.isUnresolved(props.memory) && props.memory !== undefined) {
604605
memoryInMb = props.memory.toMebibytes();
605606
if (memoryInMb % 64 !== 0) {
606-
throw new Error(`\`memory\` must be a multiple of 64 MiB, got ${memoryInMb} MiB.`);
607+
throw new ValidationError(`\`memory\` must be a multiple of 64 MiB, got ${memoryInMb} MiB.`, this);
607608
}
608609
if (memoryInMb < 960 || memoryInMb > 3008) {
609-
throw new Error(`\`memory\` must be between 960 MiB and 3008 MiB, got ${memoryInMb} MiB.`);
610+
throw new ValidationError(`\`memory\` must be between 960 MiB and 3008 MiB, got ${memoryInMb} MiB.`, this);
610611
}
611612
}
612613

613614
let timeoutInSeconds: number | undefined;
614615
if (!cdk.Token.isUnresolved(props.timeout) && props.timeout !== undefined) {
615616
const timeoutInMillis = props.timeout.toMilliseconds();
616617
if (timeoutInMillis % 1000 !== 0) {
617-
throw new Error(`\`timeout\` must be set as an integer representing seconds, got ${timeoutInMillis} milliseconds.`);
618+
throw new ValidationError(`\`timeout\` must be set as an integer representing seconds, got ${timeoutInMillis} milliseconds.`, this);
618619
}
619620

620621
timeoutInSeconds = props.timeout.toSeconds();
621622
if (timeoutInSeconds < 3 || timeoutInSeconds > 840) {
622-
throw new Error(`\`timeout\` must be between 3 seconds and 840 seconds, got ${timeoutInSeconds} seconds.`);
623+
throw new ValidationError(`\`timeout\` must be between 3 seconds and 840 seconds, got ${timeoutInSeconds} seconds.`, this);
623624
}
624625
}
625626

@@ -644,15 +645,15 @@ export class Canary extends cdk.Resource implements ec2.IConnectable {
644645
private createVpcConfig(props: CanaryProps): CfnCanary.VPCConfigProperty | undefined {
645646
if (!props.vpc) {
646647
if (props.vpcSubnets != null || props.securityGroups != null) {
647-
throw new Error("You must provide the 'vpc' prop when using VPC-related properties.");
648+
throw new ValidationError("You must provide the 'vpc' prop when using VPC-related properties.", this);
648649
}
649650

650651
return undefined;
651652
}
652653

653654
const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets);
654655
if (subnetIds.length < 1) {
655-
throw new Error('No matching subnets found in the VPC.');
656+
throw new ValidationError('No matching subnets found in the VPC.', this);
656657
}
657658

658659
let securityGroups: ec2.ISecurityGroup[];
@@ -685,12 +686,12 @@ export class Canary extends cdk.Resource implements ec2.IConnectable {
685686
props.artifactS3EncryptionMode === ArtifactsEncryptionMode.S3_MANAGED &&
686687
props.artifactS3KmsKey
687688
) {
688-
throw new Error(`A customer-managed KMS key was provided, but the encryption mode is not set to SSE-KMS, got: ${props.artifactS3EncryptionMode}.`);
689+
throw new ValidationError(`A customer-managed KMS key was provided, but the encryption mode is not set to SSE-KMS, got: ${props.artifactS3EncryptionMode}.`, this);
689690
}
690691

691692
// Only check runtime family is Node.js because versions prior to `syn-nodejs-puppeteer-3.3` are deprecated and can no longer be configured.
692693
if (!isNodeRuntime && props.artifactS3EncryptionMode) {
693-
throw new Error(`Artifact encryption is only supported for canaries that use Synthetics runtime version \`syn-nodejs-puppeteer-3.3\` or later and the Playwright runtime, got ${props.runtime.name}.`);
694+
throw new ValidationError(`Artifact encryption is only supported for canaries that use Synthetics runtime version \`syn-nodejs-puppeteer-3.3\` or later and the Playwright runtime, got ${props.runtime.name}.`, this);
694695
}
695696

696697
const encryptionMode = props.artifactS3EncryptionMode ? props.artifactS3EncryptionMode :
@@ -752,9 +753,9 @@ const nameRegex: RegExp = /^[0-9a-z_\-]+$/;
752753
*/
753754
function validateName(name: string) {
754755
if (name.length > 255) {
755-
throw new Error(`Canary name is too large, must be between 1 and 255 characters, but is ${name.length} (got "${name}")`);
756+
throw new UnscopedValidationError(`Canary name is too large, must be between 1 and 255 characters, but is ${name.length} (got "${name}")`);
756757
}
757758
if (!nameRegex.test(name)) {
758-
throw new Error(`Canary name must be lowercase, numbers, hyphens, or underscores (got "${name}")`);
759+
throw new UnscopedValidationError(`Canary name must be lowercase, numbers, hyphens, or underscores (got "${name}")`);
759760
}
760761
}

packages/aws-cdk-lib/aws-synthetics/lib/code.ts

+10-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { RuntimeFamily } from './runtime';
55
import * as s3 from '../../aws-s3';
66
import * as s3_assets from '../../aws-s3-assets';
77
import { Stage, Token } from '../../core';
8+
import { UnscopedValidationError, ValidationError } from '../../core/lib/errors';
89

910
/**
1011
* The code the canary should execute
@@ -92,7 +93,7 @@ export class AssetCode extends Code {
9293
super();
9394

9495
if (!fs.existsSync(this.assetPath)) {
95-
throw new Error(`${this.assetPath} is not a valid path`);
96+
throw new UnscopedValidationError(`${this.assetPath} is not a valid path`);
9697
}
9798
}
9899

@@ -129,7 +130,7 @@ export class AssetCode extends Code {
129130
*/
130131
private validateCanaryAsset(scope: Construct, handler: string, family: RuntimeFamily, runtimeName?: string) {
131132
if (!this.asset) {
132-
throw new Error("'validateCanaryAsset' must be called after 'this.asset' is instantiated");
133+
throw new ValidationError("'validateCanaryAsset' must be called after 'this.asset' is instantiated", scope);
133134
}
134135

135136
// Get the staged (or copied) asset path.
@@ -139,7 +140,7 @@ export class AssetCode extends Code {
139140

140141
if (path.extname(assetPath) !== '.zip') {
141142
if (!fs.lstatSync(assetPath).isDirectory()) {
142-
throw new Error(`Asset must be a .zip file or a directory (${this.assetPath})`);
143+
throw new ValidationError(`Asset must be a .zip file or a directory (${this.assetPath})`, scope);
143144
}
144145

145146
const filename = handler.split('.')[0];
@@ -151,16 +152,16 @@ export class AssetCode extends Code {
151152
const hasValidExtension = playwrightValidExtensions.some(ext => fs.existsSync(path.join(assetPath, `${filename}${ext}`)));
152153
// Requires asset directory to have the structure 'nodejs/node_modules' for puppeteer runtime.
153154
if (family === RuntimeFamily.NODEJS && runtimeName.includes('puppeteer') && !fs.existsSync(path.join(assetPath, 'nodejs', 'node_modules', nodeFilename))) {
154-
throw new Error(`The canary resource requires that the handler is present at "nodejs/node_modules/${nodeFilename}" but not found at ${this.assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs.html)`);
155+
throw new ValidationError(`The canary resource requires that the handler is present at "nodejs/node_modules/${nodeFilename}" but not found at ${this.assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs.html)`, scope);
155156
}
156157
// Requires the canary handler file to have the extension '.js', '.mjs', or '.cjs' for the playwright runtime.
157158
if (family === RuntimeFamily.NODEJS && runtimeName.includes('playwright') && !hasValidExtension) {
158-
throw new Error(`The canary resource requires that the handler is present at one of the following extensions: ${playwrightValidExtensions.join(', ')} but not found at ${this.assetPath}`);
159+
throw new ValidationError(`The canary resource requires that the handler is present at one of the following extensions: ${playwrightValidExtensions.join(', ')} but not found at ${this.assetPath}`, scope);
159160
}
160161
}
161162
// Requires the asset directory to have the structure 'python/{canary-handler-name}.py' for the Python runtime.
162163
if (family === RuntimeFamily.PYTHON && !fs.existsSync(path.join(assetPath, 'python', pythonFilename))) {
163-
throw new Error(`The canary resource requires that the handler is present at "python/${pythonFilename}" but not found at ${this.assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Python.html)`);
164+
throw new ValidationError(`The canary resource requires that the handler is present at "python/${pythonFilename}" but not found at ${this.assetPath} (https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Python.html)`, scope);
164165
}
165166
}
166167
}
@@ -174,14 +175,14 @@ export class InlineCode extends Code {
174175
super();
175176

176177
if (code.length === 0) {
177-
throw new Error('Canary inline code cannot be empty');
178+
throw new UnscopedValidationError('Canary inline code cannot be empty');
178179
}
179180
}
180181

181-
public bind(_scope: Construct, handler: string, _family: RuntimeFamily, _runtimeName?: string): CodeConfig {
182+
public bind(scope: Construct, handler: string, _family: RuntimeFamily, _runtimeName?: string): CodeConfig {
182183

183184
if (handler !== 'index.handler') {
184-
throw new Error(`The handler for inline code must be "index.handler" (got "${handler}")`);
185+
throw new ValidationError(`The handler for inline code must be "index.handler" (got "${handler}")`, scope);
185186
}
186187

187188
return {

packages/aws-cdk-lib/aws-synthetics/lib/schedule.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Duration } from '../../core';
2+
import { UnscopedValidationError } from '../../core/lib/errors';
23

34
/**
45
* Schedule for canary runs
@@ -30,7 +31,7 @@ export class Schedule {
3031
public static rate(interval: Duration): Schedule {
3132
const minutes = interval.toMinutes();
3233
if (minutes > 60) {
33-
throw new Error('Schedule duration must be between 1 and 60 minutes');
34+
throw new UnscopedValidationError('Schedule duration must be between 1 and 60 minutes');
3435
}
3536
if (minutes === 0) {
3637
return Schedule.once();
@@ -46,7 +47,7 @@ export class Schedule {
4647
*/
4748
public static cron(options: CronOptions): Schedule {
4849
if (options.weekDay !== undefined && options.day !== undefined) {
49-
throw new Error('Cannot supply both \'day\' and \'weekDay\', use at most one');
50+
throw new UnscopedValidationError('Cannot supply both \'day\' and \'weekDay\', use at most one');
5051
}
5152

5253
const minute = fallback(options.minute, '*');

0 commit comments

Comments
 (0)