Skip to content

Commit ab9dd0a

Browse files
authored
feat(appsync): throw ValidationError instead of untyped errors (#33206)
### Issue `aws-appsync ` 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 309df92 commit ab9dd0a

File tree

6 files changed

+34
-33
lines changed

6 files changed

+34
-33
lines changed

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

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const enableNoThrowDefaultErrorIn = [
2323
'aws-apigatewayv2-authorizers',
2424
'aws-apigatewayv2-integrations',
2525
'aws-applicationautoscaling',
26+
'aws-appsync',
2627
'aws-cognito',
2728
'aws-elasticloadbalancing',
2829
'aws-elasticloadbalancingv2',

packages/aws-cdk-lib/aws-appsync/lib/appsync-function.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { BaseDataSource, LambdaDataSource } from './data-source';
55
import { IGraphqlApi } from './graphqlapi-base';
66
import { MappingTemplate } from './mapping-template';
77
import { FunctionRuntime } from './runtime';
8-
import { Resource, IResource, Lazy, Fn } from '../../core';
8+
import { Resource, IResource, Lazy, Fn, ValidationError } from '../../core';
99

1010
/**
1111
* the base properties for AppSync Functions
@@ -160,15 +160,15 @@ export class AppsyncFunction extends Resource implements IAppsyncFunction {
160160

161161
// If runtime is specified, code must also be
162162
if (props.runtime && !props.code) {
163-
throw new Error('Code is required when specifying a runtime');
163+
throw new ValidationError('Code is required when specifying a runtime', scope);
164164
}
165165

166166
if (props.code && (props.requestMappingTemplate || props.responseMappingTemplate)) {
167-
throw new Error('Mapping templates cannot be used alongside code');
167+
throw new ValidationError('Mapping templates cannot be used alongside code', scope);
168168
}
169169

170170
if (props.maxBatchSize && !(props.dataSource instanceof LambdaDataSource)) {
171-
throw new Error('maxBatchSize can only be set for the data source of type \LambdaDataSource\'');
171+
throw new ValidationError('maxBatchSize can only be set for the data source of type \LambdaDataSource\'', scope);
172172
}
173173

174174
const code = props.code?.bind(this);

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,8 @@ export class AssetCode extends Code {
6868
...this.options,
6969
});
7070
} else if (cdk.Stack.of(this.asset) !== cdk.Stack.of(scope)) {
71-
throw new Error(`Asset is already associated with another stack '${cdk.Stack.of(this.asset).stackName}'. ` +
72-
'Create a new Code instance for every stack.');
71+
throw new cdk.ValidationError(`Asset is already associated with another stack '${cdk.Stack.of(this.asset).stackName}'. ` +
72+
'Create a new Code instance for every stack.', scope);
7373
}
7474

7575
return {
@@ -86,7 +86,7 @@ export class InlineCode extends Code {
8686
super();
8787

8888
if (code.length === 0) {
89-
throw new Error('AppSync Inline code cannot be empty');
89+
throw new cdk.UnscopedValidationError('AppSync Inline code cannot be empty');
9090
}
9191
}
9292

packages/aws-cdk-lib/aws-appsync/lib/graphqlapi-base.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { IFunction } from '../../aws-lambda';
1818
import { IDomain as IOpenSearchDomain } from '../../aws-opensearchservice';
1919
import { IDatabaseCluster, IServerlessCluster } from '../../aws-rds';
2020
import { ISecret } from '../../aws-secretsmanager';
21-
import { ArnFormat, CfnResource, IResource, Resource, Stack } from '../../core';
21+
import { ArnFormat, CfnResource, IResource, Resource, Stack, UnscopedValidationError } from '../../core';
2222

2323
/**
2424
* Optional configuration for data sources
@@ -64,7 +64,7 @@ export class IamResource {
6464
*/
6565
public static custom(...arns: string[]): IamResource {
6666
if (arns.length === 0) {
67-
throw new Error('At least 1 custom ARN must be provided.');
67+
throw new UnscopedValidationError('At least 1 custom ARN must be provided.');
6868
}
6969
return new IamResource(arns);
7070
}

packages/aws-cdk-lib/aws-appsync/lib/graphqlapi.ts

+18-18
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { IUserPool } from '../../aws-cognito';
88
import { ManagedPolicy, Role, IRole, ServicePrincipal } from '../../aws-iam';
99
import { IFunction } from '../../aws-lambda';
1010
import { ILogGroup, LogGroup, LogRetention, RetentionDays } from '../../aws-logs';
11-
import { CfnResource, Duration, Expiration, FeatureFlags, IResolvable, Lazy, Stack, Token } from '../../core';
11+
import { CfnResource, Duration, Expiration, FeatureFlags, IResolvable, Lazy, Stack, Token, ValidationError } from '../../core';
1212
import * as cxapi from '../../cx-api';
1313

1414
/**
@@ -578,7 +578,7 @@ export class GraphqlApi extends GraphqlApiBase {
578578
if (this.definition.schema) {
579579
return this.definition.schema;
580580
}
581-
throw new Error('Schema does not exist for AppSync merged APIs.');
581+
throw new ValidationError('Schema does not exist for AppSync merged APIs.', this);
582582
}
583583

584584
/**
@@ -619,19 +619,19 @@ export class GraphqlApi extends GraphqlApiBase {
619619
this.validateAuthorizationProps(modes);
620620

621621
if (!props.schema && !props.definition) {
622-
throw new Error('You must specify a GraphQL schema or source APIs in property definition.');
622+
throw new ValidationError('You must specify a GraphQL schema or source APIs in property definition.', this);
623623
}
624624
if ((props.schema !== undefined) === (props.definition !== undefined)) {
625-
throw new Error('You cannot specify both properties schema and definition.');
625+
throw new ValidationError('You cannot specify both properties schema and definition.', this);
626626
}
627627
if (props.queryDepthLimit !== undefined && (props.queryDepthLimit < 0 || props.queryDepthLimit > 75)) {
628-
throw new Error('You must specify a query depth limit between 0 and 75.');
628+
throw new ValidationError('You must specify a query depth limit between 0 and 75.', this);
629629
}
630630
if (props.resolverCountLimit !== undefined && (props.resolverCountLimit < 0 || props.resolverCountLimit > 10000)) {
631-
throw new Error('You must specify a resolver count limit between 0 and 10000.');
631+
throw new ValidationError('You must specify a resolver count limit between 0 and 10000.', this);
632632
}
633633
if (!Token.isUnresolved(props.ownerContact) && props.ownerContact !== undefined && (props.ownerContact.length > 256)) {
634-
throw new Error('You must specify `ownerContact` as a string of 256 characters or less.');
634+
throw new ValidationError('You must specify `ownerContact` as a string of 256 characters or less.', this);
635635
}
636636

637637
this.definition = props.schema ? Definition.fromSchema(props.schema) : props.definition!;
@@ -786,24 +786,24 @@ export class GraphqlApi extends GraphqlApiBase {
786786

787787
private validateAuthorizationProps(modes: AuthorizationMode[]) {
788788
if (modes.filter((mode) => mode.authorizationType === AuthorizationType.LAMBDA).length > 1) {
789-
throw new Error('You can only have a single AWS Lambda function configured to authorize your API.');
789+
throw new ValidationError('You can only have a single AWS Lambda function configured to authorize your API.', this);
790790
}
791791
modes.map((mode) => {
792792
if (mode.authorizationType === AuthorizationType.OIDC && !mode.openIdConnectConfig) {
793-
throw new Error('Missing OIDC Configuration');
793+
throw new ValidationError('Missing OIDC Configuration', this);
794794
}
795795
if (mode.authorizationType === AuthorizationType.USER_POOL && !mode.userPoolConfig) {
796-
throw new Error('Missing User Pool Configuration');
796+
throw new ValidationError('Missing User Pool Configuration', this);
797797
}
798798
if (mode.authorizationType === AuthorizationType.LAMBDA && !mode.lambdaAuthorizerConfig) {
799-
throw new Error('Missing Lambda Configuration');
799+
throw new ValidationError('Missing Lambda Configuration', this);
800800
}
801801
});
802802
if (modes.filter((mode) => mode.authorizationType === AuthorizationType.API_KEY).length > 1) {
803-
throw new Error('You can\'t duplicate API_KEY configuration. See https://docs.aws.amazon.com/appsync/latest/devguide/security.html');
803+
throw new ValidationError('You can\'t duplicate API_KEY configuration. See https://docs.aws.amazon.com/appsync/latest/devguide/security.html', this);
804804
}
805805
if (modes.filter((mode) => mode.authorizationType === AuthorizationType.IAM).length > 1) {
806-
throw new Error('You can\'t duplicate IAM configuration. See https://docs.aws.amazon.com/appsync/latest/devguide/security.html');
806+
throw new ValidationError('You can\'t duplicate IAM configuration. See https://docs.aws.amazon.com/appsync/latest/devguide/security.html', this);
807807
}
808808
}
809809

@@ -824,16 +824,16 @@ export class GraphqlApi extends GraphqlApiBase {
824824
*/
825825
public addEnvironmentVariable(key: string, value: string) {
826826
if (this.definition.sourceApiOptions) {
827-
throw new Error('Environment variables are not supported for merged APIs');
827+
throw new ValidationError('Environment variables are not supported for merged APIs', this);
828828
}
829829
if (!Token.isUnresolved(key) && !/^[A-Za-z]+\w*$/.test(key)) {
830-
throw new Error(`Key '${key}' must begin with a letter and can only contain letters, numbers, and underscores`);
830+
throw new ValidationError(`Key '${key}' must begin with a letter and can only contain letters, numbers, and underscores`, this);
831831
}
832832
if (!Token.isUnresolved(key) && (key.length < 2 || key.length > 64)) {
833-
throw new Error(`Key '${key}' must be between 2 and 64 characters long, got ${key.length}`);
833+
throw new ValidationError(`Key '${key}' must be between 2 and 64 characters long, got ${key.length}`, this);
834834
}
835835
if (!Token.isUnresolved(value) && value.length > 512) {
836-
throw new Error(`Value for '${key}' is too long. Values can be up to 512 characters long, got ${value.length}`);
836+
throw new ValidationError(`Value for '${key}' is too long. Values can be up to 512 characters long, got ${value.length}`, this);
837837
}
838838

839839
this.environmentVariables[key] = value;
@@ -926,7 +926,7 @@ export class GraphqlApi extends GraphqlApiBase {
926926
*/
927927
public get appSyncDomainName(): string {
928928
if (!this.domainNameResource) {
929-
throw new Error('Cannot retrieve the appSyncDomainName without a domainName configuration');
929+
throw new ValidationError('Cannot retrieve the appSyncDomainName without a domainName configuration', this);
930930
}
931931
return this.domainNameResource.attrAppSyncDomainName;
932932
}

packages/aws-cdk-lib/aws-appsync/lib/resolver.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { BaseDataSource } from './data-source';
88
import { IGraphqlApi } from './graphqlapi-base';
99
import { MappingTemplate } from './mapping-template';
1010
import { FunctionRuntime } from './runtime';
11-
import { Token } from '../../core';
11+
import { Token, ValidationError } from '../../core';
1212

1313
/**
1414
* Basic properties for an AppSync resolver
@@ -110,25 +110,25 @@ export class Resolver extends Construct {
110110

111111
// If runtime is specified, code must also be
112112
if (props.runtime && !props.code) {
113-
throw new Error('Code is required when specifying a runtime');
113+
throw new ValidationError('Code is required when specifying a runtime', scope);
114114
}
115115

116116
if (props.code && (props.requestMappingTemplate || props.responseMappingTemplate)) {
117-
throw new Error('Mapping templates cannot be used alongside code');
117+
throw new ValidationError('Mapping templates cannot be used alongside code', scope);
118118
}
119119

120120
if (pipelineConfig && props.dataSource) {
121-
throw new Error(`Pipeline Resolver cannot have data source. Received: ${props.dataSource.name}`);
121+
throw new ValidationError(`Pipeline Resolver cannot have data source. Received: ${props.dataSource.name}`, scope);
122122
}
123123

124124
if (props.cachingConfig?.ttl && (props.cachingConfig.ttl.toSeconds() < 1 || props.cachingConfig.ttl.toSeconds() > 3600)) {
125-
throw new Error(`Caching config TTL must be between 1 and 3600 seconds. Received: ${props.cachingConfig.ttl.toSeconds()}`);
125+
throw new ValidationError(`Caching config TTL must be between 1 and 3600 seconds. Received: ${props.cachingConfig.ttl.toSeconds()}`, scope);
126126
}
127127

128128
if (props.cachingConfig?.cachingKeys) {
129129
if (props.cachingConfig.cachingKeys.find(cachingKey =>
130130
!Token.isUnresolved(cachingKey) && !BASE_CACHING_KEYS.find(baseCachingKey => cachingKey.startsWith(baseCachingKey)))) {
131-
throw new Error(`Caching config keys must begin with $context.arguments, $context.source or $context.identity. Received: ${props.cachingConfig.cachingKeys}`);
131+
throw new ValidationError(`Caching config keys must begin with $context.arguments, $context.source or $context.identity. Received: ${props.cachingConfig.cachingKeys}`, scope);
132132
}
133133
}
134134

0 commit comments

Comments
 (0)