Skip to content

Commit 6677b33

Browse files
authored
feat(ssm): throw ValidationError instead of untyped errors (#33067)
### Issue `aws-ssm` + friends 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 9b4fd6b commit 6677b33

File tree

3 files changed

+31
-26
lines changed

3 files changed

+31
-26
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ const enableNoThrowDefaultErrorIn = [
2121
'aws-s3',
2222
'aws-sns',
2323
'aws-sqs',
24+
'aws-ssm',
25+
'aws-ssmcontacts',
26+
'aws-ssmincidents',
27+
'aws-ssmquicksetup',
2428
];
2529
baseConfig.overrides.push({
2630
files: enableNoThrowDefaultErrorIn.map(m => `./${m}/lib/**`),

packages/aws-cdk-lib/aws-ssm/lib/parameter.ts

+22-22
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
ContextProvider, Fn, IResource, Resource, Stack, Token,
1111
Tokenization,
1212
} from '../../core';
13+
import { ValidationError } from '../../core/lib/errors';
1314

1415
/**
1516
* An SSM Parameter reference.
@@ -479,7 +480,7 @@ export class StringParameter extends ParameterBase implements IStringParameter {
479480
*/
480481
public static fromStringParameterArn(scope: Construct, id: string, stringParameterArn: string): IStringParameter {
481482
if (Token.isUnresolved(stringParameterArn)) {
482-
throw new Error('stringParameterArn cannot be an unresolved token');
483+
throw new ValidationError('stringParameterArn cannot be an unresolved token', scope);
483484
}
484485

485486
// has to be the same region
@@ -488,13 +489,13 @@ export class StringParameter extends ParameterBase implements IStringParameter {
488489
const arnParts = stringParameterArn.split(':');
489490
const stackRegion = Stack.of(scope).region;
490491
if (arnParts.length !== 6) {
491-
throw new Error('unexpected StringParameterArn format');
492+
throw new ValidationError('unexpected StringParameterArn format', scope);
492493
} else if (Token.isUnresolved(stackRegion)) {
493494
// Region is unknown during synthesis, emit a warning for visibility
494495
Annotations.of(scope).addWarningV2('aws-cdk-lib/aws-ssm:crossAccountReferenceSameRegion', 'Cross-account references will only work within the same region');
495496
} else if (!Token.isUnresolved(stackRegion) && arnParts[3] !== stackRegion) {
496497
// If the region is known, it must match the region specified in the ARN string
497-
throw new Error('stringParameterArn must be in the same region as the stack');
498+
throw new ValidationError('stringParameterArn must be in the same region as the stack', scope);
498499
}
499500

500501
const parameterType = ParameterValueType.STRING;
@@ -516,10 +517,10 @@ export class StringParameter extends ParameterBase implements IStringParameter {
516517
*/
517518
public static fromStringParameterAttributes(scope: Construct, id: string, attrs: StringParameterAttributes): IStringParameter {
518519
if (!attrs.parameterName) {
519-
throw new Error('parameterName cannot be an empty string');
520+
throw new ValidationError('parameterName cannot be an empty string', scope);
520521
}
521522
if (attrs.type && ![ParameterType.STRING, ParameterType.AWS_EC2_IMAGE_ID].includes(attrs.type)) {
522-
throw new Error(`fromStringParameterAttributes does not support ${attrs.type}. Please use ParameterType.STRING or ParameterType.AWS_EC2_IMAGE_ID`);
523+
throw new ValidationError(`fromStringParameterAttributes does not support ${attrs.type}. Please use ParameterType.STRING or ParameterType.AWS_EC2_IMAGE_ID`, scope);
523524
}
524525

525526
const type = attrs.type ?? attrs.valueType ?? ParameterValueType.STRING;
@@ -627,8 +628,8 @@ export class StringParameter extends ParameterBase implements IStringParameter {
627628
*/
628629
public static valueForTypedStringParameter(scope: Construct, parameterName: string, type = ParameterType.STRING, version?: number): string {
629630
if (type === ParameterType.STRING_LIST) {
630-
throw new Error('valueForTypedStringParameter does not support STRING_LIST, '
631-
+'use valueForTypedListParameter instead');
631+
throw new ValidationError('valueForTypedStringParameter does not support STRING_LIST, '
632+
+'use valueForTypedListParameter instead', scope);
632633
}
633634
const stack = Stack.of(scope);
634635
const id = makeIdentityForImportedValue(parameterName);
@@ -666,17 +667,17 @@ export class StringParameter extends ParameterBase implements IStringParameter {
666667
});
667668

668669
if (props.allowedPattern) {
669-
_assertValidValue(props.stringValue, props.allowedPattern);
670+
_assertValidValue(this, props.stringValue, props.allowedPattern);
670671
}
671672

672-
validateParameterName(this.physicalName);
673+
validateParameterName(this, this.physicalName);
673674

674675
if (props.description && props.description?.length > 1024) {
675-
throw new Error('Description cannot be longer than 1024 characters.');
676+
throw new ValidationError('Description cannot be longer than 1024 characters.', this);
676677
}
677678

678679
if (props.type && props.type === ParameterType.AWS_EC2_IMAGE_ID) {
679-
throw new Error('The type must either be ParameterType.STRING or ParameterType.STRING_LIST. Did you mean to set dataType: ParameterDataType.AWS_EC2_IMAGE instead?');
680+
throw new ValidationError('The type must either be ParameterType.STRING or ParameterType.STRING_LIST. Did you mean to set dataType: ParameterDataType.AWS_EC2_IMAGE instead?', this);
680681
}
681682

682683
const resource = new ssm.CfnParameter(this, 'Resource', {
@@ -705,7 +706,6 @@ export class StringParameter extends ParameterBase implements IStringParameter {
705706
* @resource AWS::SSM::Parameter
706707
*/
707708
export class StringListParameter extends ParameterBase implements IStringListParameter {
708-
709709
/**
710710
* Imports an external parameter of type string list.
711711
* Returns a token and should not be parsed.
@@ -726,7 +726,7 @@ export class StringListParameter extends ParameterBase implements IStringListPar
726726
*/
727727
public static fromListParameterAttributes(scope: Construct, id: string, attrs: ListParameterAttributes): IStringListParameter {
728728
if (!attrs.parameterName) {
729-
throw new Error('parameterName cannot be an empty string');
729+
throw new ValidationError('parameterName cannot be an empty string', scope);
730730
}
731731

732732
const type = attrs.elementType ?? ParameterValueType.STRING;
@@ -774,17 +774,17 @@ export class StringListParameter extends ParameterBase implements IStringListPar
774774
});
775775

776776
if (props.stringListValue.find(str => !Token.isUnresolved(str) && str.indexOf(',') !== -1)) {
777-
throw new Error('Values of a StringList SSM Parameter cannot contain the \',\' character. Use a string parameter instead.');
777+
throw new ValidationError('Values of a StringList SSM Parameter cannot contain the \',\' character. Use a string parameter instead.', this);
778778
}
779779

780780
if (props.allowedPattern && !Token.isUnresolved(props.stringListValue)) {
781-
props.stringListValue.forEach(str => _assertValidValue(str, props.allowedPattern!));
781+
props.stringListValue.forEach(str => _assertValidValue(this, str, props.allowedPattern!));
782782
}
783783

784-
validateParameterName(this.physicalName);
784+
validateParameterName(this, this.physicalName);
785785

786786
if (props.description && props.description?.length > 1024) {
787-
throw new Error('Description cannot be longer than 1024 characters.');
787+
throw new ValidationError('Description cannot be longer than 1024 characters.', this);
788788
}
789789

790790
const resource = new ssm.CfnParameter(this, 'Resource', {
@@ -815,26 +815,26 @@ export class StringListParameter extends ParameterBase implements IStringListPar
815815
* @throws if the ``value`` does not conform to the ``allowedPattern`` and neither is an unresolved token (per
816816
* ``cdk.unresolved``).
817817
*/
818-
function _assertValidValue(value: string, allowedPattern: string): void {
818+
function _assertValidValue(scope: Construct, value: string, allowedPattern: string): void {
819819
if (Token.isUnresolved(value) || Token.isUnresolved(allowedPattern)) {
820820
// Unable to perform validations against unresolved tokens
821821
return;
822822
}
823823
if (!new RegExp(allowedPattern).test(value)) {
824-
throw new Error(`The supplied value (${value}) does not match the specified allowedPattern (${allowedPattern})`);
824+
throw new ValidationError(`The supplied value (${value}) does not match the specified allowedPattern (${allowedPattern})`, scope);
825825
}
826826
}
827827

828828
function makeIdentityForImportedValue(parameterName: string) {
829829
return `SsmParameterValue:${parameterName}:C96584B6-F00A-464E-AD19-53AFF4B05118`;
830830
}
831831

832-
function validateParameterName(parameterName: string) {
832+
function validateParameterName(scope: Construct, parameterName: string) {
833833
if (Token.isUnresolved(parameterName)) { return; }
834834
if (parameterName.length > 2048) {
835-
throw new Error('name cannot be longer than 2048 characters.');
835+
throw new ValidationError('name cannot be longer than 2048 characters.', scope);
836836
}
837837
if (!parameterName.match(/^[\/\w.-]+$/)) {
838-
throw new Error(`name must only contain letters, numbers, and the following 4 symbols .-_/; got ${parameterName}`);
838+
throw new ValidationError(`name must only contain letters, numbers, and the following 4 symbols .-_/; got ${parameterName}`, scope);
839839
}
840840
}

packages/aws-cdk-lib/aws-ssm/lib/util.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { IConstruct } from 'constructs';
22
import { ArnFormat, Stack, Token } from '../../core';
3+
import { ValidationError } from '../../core/lib/errors';
34

45
export const AUTOGEN_MARKER = '$$autogen$$';
56

@@ -19,7 +20,7 @@ export function arnForParameterName(scope: IConstruct, parameterName: string, op
1920
const nameToValidate = physicalName || parameterName;
2021

2122
if (!Token.isUnresolved(nameToValidate) && nameToValidate.includes('/') && !nameToValidate.startsWith('/')) {
22-
throw new Error(`Parameter names must be fully qualified (if they include "/" they must also begin with a "/"): ${nameToValidate}`);
23+
throw new ValidationError(`Parameter names must be fully qualified (if they include "/" they must also begin with a "/"): ${nameToValidate}`, scope);
2324
}
2425

2526
if (isSimpleName()) {
@@ -48,7 +49,7 @@ export function arnForParameterName(scope: IConstruct, parameterName: string, op
4849
if (!concreteName || Token.isUnresolved(concreteName)) {
4950

5051
if (options.simpleName === undefined) {
51-
throw new Error('Unable to determine ARN separator for SSM parameter since the parameter name is an unresolved token. Use "fromAttributes" and specify "simpleName" explicitly');
52+
throw new ValidationError('Unable to determine ARN separator for SSM parameter since the parameter name is an unresolved token. Use "fromAttributes" and specify "simpleName" explicitly', scope);
5253
}
5354

5455
return options.simpleName;
@@ -60,10 +61,10 @@ export function arnForParameterName(scope: IConstruct, parameterName: string, op
6061
if (options.simpleName !== undefined && options.simpleName !== result) {
6162

6263
if (concreteName === AUTOGEN_MARKER) {
63-
throw new Error('If "parameterName" is not explicitly defined, "simpleName" must be "true" or undefined since auto-generated parameter names always have simple names');
64+
throw new ValidationError('If "parameterName" is not explicitly defined, "simpleName" must be "true" or undefined since auto-generated parameter names always have simple names', scope);
6465
}
6566

66-
throw new Error(`Parameter name "${concreteName}" is ${result ? 'a simple name' : 'not a simple name'}, but "simpleName" was explicitly set to ${options.simpleName}. Either omit it or set it to ${result}`);
67+
throw new ValidationError(`Parameter name "${concreteName}" is ${result ? 'a simple name' : 'not a simple name'}, but "simpleName" was explicitly set to ${options.simpleName}. Either omit it or set it to ${result}`, scope);
6768
}
6869

6970
return result;

0 commit comments

Comments
 (0)