Skip to content

Commit e828389

Browse files
authored
feat(stepfunctions): throw ValidationErrors instead of untyped errors (#34438)
### Issue Relates to #32569 ### Reason for this change untyped Errors are not recommended ### Description of changes `ValidationError`s 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 28048b3 commit e828389

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+223
-203
lines changed

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ const noThrowDefaultErrorNotYetSupported = [
3232
'aws-secretsmanager',
3333
'aws-servicecatalog',
3434
'aws-sns-subscriptions',
35-
'aws-stepfunctions',
36-
'aws-stepfunctions-tasks',
3735
'core',
3836
'custom-resources',
3937
'region-info',

packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/apigateway/base.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Construct } from 'constructs';
22
import { AuthType, CallApiGatewayEndpointBaseProps } from './base-types';
33
import * as iam from '../../../aws-iam';
44
import * as sfn from '../../../aws-stepfunctions';
5+
import { ValidationError } from '../../../core';
56
import { integrationResourceArn, validatePatternSupported } from '../private/task-utils';
67

78
/**
@@ -30,7 +31,7 @@ export abstract class CallApiGatewayEndpointBase extends sfn.TaskStateBase {
3031

3132
if (this.integrationPattern === sfn.IntegrationPattern.WAIT_FOR_TASK_TOKEN) {
3233
if (!sfn.FieldUtils.containsTaskToken(this.baseProps.headers)) {
33-
throw new Error('Task Token is required in `headers` for WAIT_FOR_TASK_TOKEN pattern. Use JsonPath.taskToken to set the token.');
34+
throw new ValidationError('Task Token is required in `headers` for WAIT_FOR_TASK_TOKEN pattern. Use JsonPath.taskToken to set the token.', this);
3435
}
3536
}
3637
}

packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/athena/start-query-execution.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as kms from '../../../aws-kms';
44
import * as s3 from '../../../aws-s3';
55
import * as sfn from '../../../aws-stepfunctions';
66
import * as cdk from '../../../core';
7+
import { ValidationError } from '../../../core';
78
import { integrationResourceArn, validatePatternSupported } from '../private/task-utils';
89

910
interface AthenaStartQueryExecutionOptions {
@@ -117,23 +118,23 @@ export class AthenaStartQueryExecution extends sfn.TaskStateBase {
117118
private validateExecutionParameters(executionParameters?: string[]) {
118119
if (executionParameters === undefined || cdk.Token.isUnresolved(executionParameters)) return;
119120
if (executionParameters.length == 0) {
120-
throw new Error('\'executionParameters\' must be a non-empty list');
121+
throw new ValidationError('\'executionParameters\' must be a non-empty list', this);
121122
}
122123
const invalidExecutionParameters = executionParameters.some(p => p.length < 1 || p.length > 1024);
123124
if (invalidExecutionParameters) {
124-
throw new Error('\'executionParameters\' items\'s length must be between 1 and 1024 characters');
125+
throw new ValidationError('\'executionParameters\' items\'s length must be between 1 and 1024 characters', this);
125126
}
126127
}
127128

128129
private validateMaxAgeInMinutes(resultReuseConfigurationMaxAge?: cdk.Duration) {
129130
if (resultReuseConfigurationMaxAge === undefined || cdk.Token.isUnresolved(resultReuseConfigurationMaxAge)) return;
130131
const maxAgeInMillis = resultReuseConfigurationMaxAge.toMilliseconds();
131132
if (maxAgeInMillis > 0 && maxAgeInMillis < cdk.Duration.minutes(1).toMilliseconds()) {
132-
throw new Error(`resultReuseConfigurationMaxAge must be greater than or equal to 1 minute or be equal to 0, got ${maxAgeInMillis} ms`);
133+
throw new ValidationError(`resultReuseConfigurationMaxAge must be greater than or equal to 1 minute or be equal to 0, got ${maxAgeInMillis} ms`, this);
133134
}
134135
const maxAgeInMinutes = resultReuseConfigurationMaxAge.toMinutes();
135136
if (maxAgeInMinutes > 10080) {
136-
throw new Error(`resultReuseConfigurationMaxAge must either be 0 or between 1 and 10080 minutes, got ${maxAgeInMinutes}`);
137+
throw new ValidationError(`resultReuseConfigurationMaxAge must either be 0 or between 1 and 10080 minutes, got ${maxAgeInMinutes}`, this);
137138
}
138139
}
139140

packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/aws-sdk/call-aws-service.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Construct } from 'constructs';
22
import * as iam from '../../../aws-iam';
33
import * as sfn from '../../../aws-stepfunctions';
4-
import { Token } from '../../../core';
4+
import { Token, ValidationError } from '../../../core';
55
import { integrationResourceArn } from '../private/task-utils';
66

77
interface CallAwsServiceOptions {
@@ -109,15 +109,15 @@ export class CallAwsService extends sfn.TaskStateBase {
109109
super(scope, id, props);
110110

111111
if (this.props.integrationPattern === sfn.IntegrationPattern.RUN_JOB) {
112-
throw new Error('The RUN_JOB integration pattern is not supported for CallAwsService');
112+
throw new ValidationError('The RUN_JOB integration pattern is not supported for CallAwsService', this);
113113
}
114114
if (!Token.isUnresolved(this.props.action) && !this.props.action.startsWith(this.props.action[0]?.toLowerCase())) {
115-
throw new Error(`action must be camelCase, got: ${this.props.action}`);
115+
throw new ValidationError(`action must be camelCase, got: ${this.props.action}`, this);
116116
}
117117
if (this.props.parameters) {
118118
const invalidKeys = Object.keys(this.props.parameters).filter(key => !key.startsWith(key[0]?.toUpperCase()));
119119
if (invalidKeys.length) {
120-
throw new Error(`parameter names must be PascalCase, got: ${invalidKeys.join(', ')}`);
120+
throw new ValidationError(`parameter names must be PascalCase, got: ${invalidKeys.join(', ')}`, this);
121121
}
122122
}
123123

packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/batch/run-batch-job.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as ec2 from '../../../aws-ec2';
22
import * as iam from '../../../aws-iam';
33
import * as sfn from '../../../aws-stepfunctions';
4-
import { Duration, Stack, withResolved } from '../../../core';
4+
import { Duration, Stack, UnscopedValidationError, withResolved } from '../../../core';
55
import { getResourceArn } from '../resource-arn-suffix';
66

77
/**
@@ -188,34 +188,34 @@ export class RunBatchJob implements sfn.IStepFunctionsTask {
188188
];
189189

190190
if (!supportedPatterns.includes(this.integrationPattern)) {
191-
throw new Error(
191+
throw new UnscopedValidationError(
192192
`Invalid Service Integration Pattern: ${this.integrationPattern} is not supported to call RunBatchJob.`,
193193
);
194194
}
195195

196196
// validate arraySize limits
197197
withResolved(props.arraySize, (arraySize) => {
198198
if (arraySize !== undefined && (arraySize < 2 || arraySize > 10_000)) {
199-
throw new Error(`arraySize must be between 2 and 10,000. Received ${arraySize}.`);
199+
throw new UnscopedValidationError(`arraySize must be between 2 and 10,000. Received ${arraySize}.`);
200200
}
201201
});
202202

203203
// validate dependency size
204204
if (props.dependsOn && props.dependsOn.length > 20) {
205-
throw new Error(`dependencies must be 20 or less. Received ${props.dependsOn.length}.`);
205+
throw new UnscopedValidationError(`dependencies must be 20 or less. Received ${props.dependsOn.length}.`);
206206
}
207207

208208
// validate attempts
209209
withResolved(props.attempts, (attempts) => {
210210
if (attempts !== undefined && (attempts < 1 || attempts > 10)) {
211-
throw new Error(`attempts must be between 1 and 10. Received ${attempts}.`);
211+
throw new UnscopedValidationError(`attempts must be between 1 and 10. Received ${attempts}.`);
212212
}
213213
});
214214

215215
// validate timeout
216216
props.timeout !== undefined && withResolved(props.timeout.toSeconds(), (timeout) => {
217217
if (timeout < 60) {
218-
throw new Error(`timeout must be greater than 60 seconds. Received ${timeout} seconds.`);
218+
throw new UnscopedValidationError(`timeout must be greater than 60 seconds. Received ${timeout} seconds.`);
219219
}
220220
});
221221

@@ -224,7 +224,7 @@ export class RunBatchJob implements sfn.IStepFunctionsTask {
224224
if (props.containerOverrides?.environment) {
225225
Object.keys(props.containerOverrides.environment).forEach(key => {
226226
if (key.match(/^AWS_BATCH/)) {
227-
throw new Error(
227+
throw new UnscopedValidationError(
228228
`Invalid environment variable name: ${key}. Environment variable names starting with 'AWS_BATCH' are reserved.`,
229229
);
230230
}

packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/batch/submit-job.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Construct } from 'constructs';
22
import * as ec2 from '../../../aws-ec2';
33
import * as iam from '../../../aws-iam';
44
import * as sfn from '../../../aws-stepfunctions';
5-
import { Size, Stack, withResolved } from '../../../core';
5+
import { Size, Stack, ValidationError, withResolved } from '../../../core';
66
import { integrationResourceArn, isJsonPathOrJsonataExpression, validatePatternSupported } from '../private/task-utils';
77

88
/**
@@ -210,19 +210,19 @@ export class BatchSubmitJob extends sfn.TaskStateBase {
210210
// validate arraySize limits
211211
withResolved(props.arraySize, (arraySize) => {
212212
if (arraySize !== undefined && (arraySize < 2 || arraySize > 10_000)) {
213-
throw new Error(`arraySize must be between 2 and 10,000. Received ${arraySize}.`);
213+
throw new ValidationError(`arraySize must be between 2 and 10,000. Received ${arraySize}.`, this);
214214
}
215215
});
216216

217217
// validate dependency size
218218
if (props.dependsOn && props.dependsOn.length > 20) {
219-
throw new Error(`dependencies must be 20 or less. Received ${props.dependsOn.length}.`);
219+
throw new ValidationError(`dependencies must be 20 or less. Received ${props.dependsOn.length}.`, this);
220220
}
221221

222222
// validate attempts
223223
withResolved(props.attempts, (attempts) => {
224224
if (attempts !== undefined && (attempts < 1 || attempts > 10)) {
225-
throw new Error(`attempts must be between 1 and 10. Received ${attempts}.`);
225+
throw new ValidationError(`attempts must be between 1 and 10. Received ${attempts}.`, this);
226226
}
227227
});
228228

@@ -232,7 +232,7 @@ export class BatchSubmitJob extends sfn.TaskStateBase {
232232
props.taskTimeout?.seconds, (timeout, taskTimeout) => {
233233
const definedTimeout = timeout ?? taskTimeout;
234234
if (definedTimeout && definedTimeout < 60) {
235-
throw new Error(`attempt duration must be greater than 60 seconds. Received ${definedTimeout} seconds.`);
235+
throw new ValidationError(`attempt duration must be greater than 60 seconds. Received ${definedTimeout} seconds.`, this);
236236
}
237237
});
238238

@@ -241,8 +241,8 @@ export class BatchSubmitJob extends sfn.TaskStateBase {
241241
if (props.containerOverrides?.environment) {
242242
Object.keys(props.containerOverrides.environment).forEach(key => {
243243
if (key.match(/^AWS_BATCH/)) {
244-
throw new Error(
245-
`Invalid environment variable name: ${key}. Environment variable names starting with 'AWS_BATCH' are reserved.`,
244+
throw new ValidationError(
245+
`Invalid environment variable name: ${key}. Environment variable names starting with 'AWS_BATCH' are reserved.`, this,
246246
);
247247
}
248248
});
@@ -382,14 +382,14 @@ export class BatchSubmitJob extends sfn.TaskStateBase {
382382
if (tags === undefined) return;
383383
const tagEntries = Object.entries(tags);
384384
if (tagEntries.length > 50) {
385-
throw new Error(`Maximum tag number of entries is 50. Received ${tagEntries.length}.`);
385+
throw new ValidationError(`Maximum tag number of entries is 50. Received ${tagEntries.length}.`, this);
386386
}
387387
for (const [key, value] of tagEntries) {
388388
if (key.length < 1 || key.length > 128) {
389-
throw new Error(`Tag key size must be between 1 and 128, but got ${key.length}.`);
389+
throw new ValidationError(`Tag key size must be between 1 and 128, but got ${key.length}.`, this);
390390
}
391391
if (value.length > 256) {
392-
throw new Error(`Tag value maximum size is 256, but got ${value.length}.`);
392+
throw new ValidationError(`Tag value maximum size is 256, but got ${value.length}.`, this);
393393
}
394394
}
395395
}

packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/bedrock/guardrail.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Arn, ArnFormat, Token } from '../../../core';
1+
import { Arn, ArnFormat, Token, UnscopedValidationError } from '../../../core';
22

33
/**
44
* Guradrail settings for BedrockInvokeModel
@@ -13,7 +13,7 @@ export class Guardrail {
1313
public static enable(identifier: string, version: number): Guardrail {
1414
if (!Token.isUnresolved(version)) {
1515
if (version < 1 || version > 99999999) {
16-
throw new Error(`\`version\` must be between 1 and 99999999, got ${version}.`);
16+
throw new UnscopedValidationError(`\`version\` must be between 1 and 99999999, got ${version}.`);
1717
}
1818
}
1919
return new Guardrail(identifier, version.toString());
@@ -39,7 +39,7 @@ export class Guardrail {
3939
if (guardrailIdentifier.startsWith('arn:')) {
4040
const arn = Arn.split(guardrailIdentifier, ArnFormat.SLASH_RESOURCE_NAME);
4141
if (!arn.resourceName) {
42-
throw new Error(`Invalid ARN format. The ARN of Guradrail should have the format: \`arn:<partition>:bedrock:<region>:<account-id>:guardrail/<guardrail-name>\`, got ${guardrailIdentifier}.`);
42+
throw new UnscopedValidationError(`Invalid ARN format. The ARN of Guradrail should have the format: \`arn:<partition>:bedrock:<region>:<account-id>:guardrail/<guardrail-name>\`, got ${guardrailIdentifier}.`);
4343
}
4444
gurdrailId = arn.resourceName;
4545
} else {
@@ -48,11 +48,11 @@ export class Guardrail {
4848

4949
const guardrailPattern = /^[a-z0-9]+$/;
5050
if (!guardrailPattern.test(gurdrailId)) {
51-
throw new Error(`The id of Guardrail must contain only lowercase letters and numbers, got ${gurdrailId}.`);
51+
throw new UnscopedValidationError(`The id of Guardrail must contain only lowercase letters and numbers, got ${gurdrailId}.`);
5252
}
5353

5454
if (guardrailIdentifier.length > 2048) {
55-
throw new Error(`\`guardrailIdentifier\` length must be between 0 and 2048, got ${guardrailIdentifier.length}.`);
55+
throw new UnscopedValidationError(`\`guardrailIdentifier\` length must be between 0 and 2048, got ${guardrailIdentifier.length}.`);
5656
}
5757
}
5858
}

packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/bedrock/invoke-model.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as bedrock from '../../../aws-bedrock';
44
import * as iam from '../../../aws-iam';
55
import * as s3 from '../../../aws-s3';
66
import * as sfn from '../../../aws-stepfunctions';
7-
import { Annotations, Stack, FeatureFlags } from '../../../core';
7+
import { Annotations, Stack, FeatureFlags, ValidationError } from '../../../core';
88
import * as cxapi from '../../../cx-api';
99
import { integrationResourceArn, validatePatternSupported } from '../private/task-utils';
1010

@@ -203,22 +203,22 @@ export class BedrockInvokeModel extends sfn.TaskStateBase {
203203
}
204204

205205
if (isBodySpecified && isInputSpecified) {
206-
throw new Error('Either `body` or `input` must be specified, but not both.');
206+
throw new ValidationError('Either `body` or `input` must be specified, but not both.', this);
207207
}
208208
if (!isBodySpecified && !isInputSpecified) {
209-
throw new Error('Either `body` or `input` must be specified.');
209+
throw new ValidationError('Either `body` or `input` must be specified.', this);
210210
}
211211
if (props.input?.s3Location?.objectVersion !== undefined) {
212-
throw new Error('Input S3 object version is not supported.');
212+
throw new ValidationError('Input S3 object version is not supported.', this);
213213
}
214214
if (props.output?.s3Location?.objectVersion !== undefined) {
215-
throw new Error('Output S3 object version is not supported.');
215+
throw new ValidationError('Output S3 object version is not supported.', this);
216216
}
217217
if (props.input?.s3InputUri && props.input.s3Location || props.output?.s3OutputUri && props.output.s3Location) {
218-
throw new Error('Either specify S3 Uri or S3 location, but not both.');
218+
throw new ValidationError('Either specify S3 Uri or S3 location, but not both.', this);
219219
}
220220
if (useNewS3UriParamsForTask && (props.input?.s3InputUri === '' || props.output?.s3OutputUri === '')) {
221-
throw new Error('S3 Uri cannot be an empty string');
221+
throw new ValidationError('S3 Uri cannot be an empty string', this);
222222
}
223223

224224
// Warning to let users know about the newly introduced props

packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/codebuild/start-build-batch.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export class CodeBuildStartBuildBatch extends sfn.TaskStateBase {
124124
];
125125
break;
126126
default:
127-
throw new Error(`Unsupported integration pattern: ${this.integrationPattern}`);
127+
throw new cdk.ValidationError(`Unsupported integration pattern: ${this.integrationPattern}`, this);
128128
}
129129

130130
return policyStatements;

packages/aws-cdk-lib/aws-stepfunctions-tasks/lib/dynamodb/private/utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as sfn from '../../../../aws-stepfunctions';
2+
import { UnscopedValidationError } from '../../../../core';
23
import { integrationResourceArn } from '../../private/task-utils';
34
import { DynamoAttributeValue } from '../shared-types';
45

@@ -25,12 +26,12 @@ export function transformAttributeValueMap(attrMap?: { [key: string]: DynamoAttr
2526

2627
export function validateJsonPath(value: string) {
2728
if (!value.startsWith('$')) {
28-
throw new Error("Data JSON path values must either be exactly equal to '$' or start with '$.'");
29+
throw new UnscopedValidationError("Data JSON path values must either be exactly equal to '$' or start with '$.'");
2930
}
3031
}
3132

3233
export function validateJsonata(value: string) {
3334
if (!value.startsWith('{%') || !value.endsWith('%}')) {
34-
throw new Error("Data JSONata expression values must either be exactly start with '{%' and end with '%}'");
35+
throw new UnscopedValidationError("Data JSONata expression values must either be exactly start with '{%' and end with '%}'");
3536
}
3637
}

0 commit comments

Comments
 (0)