Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(elbv2): throw ValidationError intsead of untyped errors #33111

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/aws-cdk-lib/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const enableNoThrowDefaultErrorIn = [
'aws-ssmincidents',
'aws-ssmquicksetup',
'aws-synthetics',
'aws-elasticloadbalancing',
'aws-elasticloadbalancingv2',
'aws-elasticloadbalancingv2-actions',
'aws-elasticloadbalancingv2-targets',
];
baseConfig.overrides.push({
files: enableNoThrowDefaultErrorIn.map(m => `./${m}/lib/**`),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
SecurityGroup, SelectedSubnets, SubnetSelection, SubnetType,
} from '../../aws-ec2';
import { Duration, Lazy, Resource } from '../../core';
import { ValidationError } from '../../core/lib/errors';

/**
* Construction properties for a LoadBalancer
Expand Down Expand Up @@ -289,9 +290,9 @@ export class LoadBalancer extends Resource implements IConnectable {
*/
public addListener(listener: LoadBalancerListener): ListenerPort {
if (listener.sslCertificateArn && listener.sslCertificateId) {
throw new Error('"sslCertificateId" is deprecated, please use "sslCertificateArn" only.');
throw new ValidationError('"sslCertificateId" is deprecated, please use "sslCertificateArn" only.', this);
}
const protocol = ifUndefinedLazy(listener.externalProtocol, () => wellKnownProtocol(listener.externalPort));
const protocol = ifUndefinedLazy(listener.externalProtocol, () => wellKnownProtocol(this, listener.externalPort));
const instancePort = listener.internalPort || listener.externalPort;
const sslCertificateArn = listener.sslCertificateArn || listener.sslCertificateId;
const instanceProtocol = ifUndefined(listener.internalProtocol,
Expand Down Expand Up @@ -449,10 +450,10 @@ export class ListenerPort implements IConnectable {
}
}

function wellKnownProtocol(port: number): LoadBalancingProtocol {
function wellKnownProtocol(scope: Construct, port: number): LoadBalancingProtocol {
const proto = tryWellKnownProtocol(port);
if (!proto) {
throw new Error(`Please supply protocol to go with port ${port}`);
throw new ValidationError(`Please supply protocol to go with port ${port}`, scope);
}
return proto;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IApplicationListener } from './application-listener';
import { IApplicationTargetGroup } from './application-target-group';
import { Port } from '../../../aws-ec2';
import { Duration, SecretValue, Token, Tokenization } from '../../../core';
import { UnscopedValidationError } from '../../../core/lib/errors';
import { CfnListener, CfnListenerRule } from '../elasticloadbalancingv2.generated';
import { IListenerAction } from '../shared/listener-action';

Expand Down Expand Up @@ -39,7 +40,7 @@ export class ListenerAction implements IListenerAction {
*/
public static forward(targetGroups: IApplicationTargetGroup[], options: ForwardOptions = {}): ListenerAction {
if (targetGroups.length === 0) {
throw new Error('Need at least one targetGroup in a ListenerAction.forward()');
throw new UnscopedValidationError('Need at least one targetGroup in a ListenerAction.forward()');
}
if (targetGroups.length === 1 && options.stickinessDuration === undefined) {
// Render a "simple" action for backwards compatibility with old templates
Expand All @@ -59,7 +60,7 @@ export class ListenerAction implements IListenerAction {
*/
public static weightedForward(targetGroups: WeightedTargetGroup[], options: ForwardOptions = {}): ListenerAction {
if (targetGroups.length === 0) {
throw new Error('Need at least one targetGroup in a ListenerAction.weightedForward()');
throw new UnscopedValidationError('Need at least one targetGroup in a ListenerAction.weightedForward()');
}

return new TargetGroupListenerAction(targetGroups.map(g => g.targetGroup), {
Expand Down Expand Up @@ -113,10 +114,10 @@ export class ListenerAction implements IListenerAction {
*/
public static redirect(options: RedirectOptions): ListenerAction {
if ([options.host, options.path, options.port, options.protocol, options.query].findIndex(x => x !== undefined) === -1) {
throw new Error('To prevent redirect loops, set at least one of \'protocol\', \'host\', \'port\', \'path\', or \'query\'.');
throw new UnscopedValidationError('To prevent redirect loops, set at least one of \'protocol\', \'host\', \'port\', \'path\', or \'query\'.');
}
if (options.path && !Token.isUnresolved(options.path) && !options.path.startsWith('/')) {
throw new Error(`Redirect path must start with a \'/\', got: ${options.path}`);
throw new UnscopedValidationError(`Redirect path must start with a \'/\', got: ${options.path}`);
}

return new ListenerAction({
Expand Down Expand Up @@ -200,7 +201,7 @@ export class ListenerAction implements IListenerAction {
*/
protected addRuleAction(actionJson: CfnListenerRule.ActionProperty) {
if (this._actionJson) {
throw new Error('rule action is already set');
throw new UnscopedValidationError('rule action is already set');
}
this._actionJson = actionJson;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Construct } from 'constructs';
import { IApplicationListener } from './application-listener';
import { CfnListenerCertificate } from '../elasticloadbalancingv2.generated';
import { IListenerCertificate } from '../shared/listener-certificate';
import { ValidationError } from '../../../core/lib/errors';

/**
* Properties for adding a set of certificates to a listener
Expand Down Expand Up @@ -40,7 +41,7 @@ export class ApplicationListenerCertificate extends Construct {
super(scope, id);

if (!props.certificateArns && !props.certificates) {
throw new Error('At least one of \'certificateArns\' or \'certificates\' is required');
throw new ValidationError('At least one of \'certificateArns\' or \'certificates\' is required', this);
}

const certificates = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ListenerAction } from './application-listener-action';
import { IApplicationTargetGroup } from './application-target-group';
import { ListenerCondition } from './conditions';
import * as cdk from '../../../core';
import { UnscopedValidationError, ValidationError } from '../../../core/lib/errors';
import { CfnListenerRule } from '../elasticloadbalancingv2.generated';
import { IListenerAction } from '../shared/listener-action';

Expand Down Expand Up @@ -216,17 +217,17 @@ export class ApplicationListenerRule extends Construct {

const hasPathPatterns = props.pathPatterns || props.pathPattern;
if (this.conditions.length === 0 && !props.hostHeader && !hasPathPatterns) {
throw new Error('At least one of \'conditions\', \'hostHeader\', \'pathPattern\' or \'pathPatterns\' is required when defining a load balancing rule.');
throw new ValidationError('At least one of \'conditions\', \'hostHeader\', \'pathPattern\' or \'pathPatterns\' is required when defining a load balancing rule.', this);
}

const possibleActions: Array<keyof ApplicationListenerRuleProps> = ['action', 'targetGroups', 'fixedResponse', 'redirectResponse'];
const providedActions = possibleActions.filter(action => props[action] !== undefined);
if (providedActions.length > 1) {
throw new Error(`'${providedActions}' specified together, specify only one`);
throw new ValidationError(`'${providedActions}' specified together, specify only one`, this);
}

if (!cdk.Token.isUnresolved(props.priority) && props.priority <= 0) {
throw new Error('Priority must have value greater than or equal to 1');
throw new ValidationError('Priority must have value greater than or equal to 1', this);
}

this.listener = props.listener;
Expand All @@ -244,7 +245,7 @@ export class ApplicationListenerRule extends Construct {

if (hasPathPatterns) {
if (props.pathPattern && props.pathPatterns) {
throw new Error('Both `pathPatterns` and `pathPattern` are specified, specify only one');
throw new ValidationError('Both `pathPatterns` and `pathPattern` are specified, specify only one', this);
}
const pathPattern = props.pathPattern ? [props.pathPattern] : props.pathPatterns;
this.setCondition('path-pattern', pathPattern);
Expand Down Expand Up @@ -393,11 +394,11 @@ export class ApplicationListenerRule extends Construct {
*/
function validateFixedResponse(fixedResponse: FixedResponse) {
if (fixedResponse.statusCode && !/^(2|4|5)\d\d$/.test(fixedResponse.statusCode)) {
throw new Error('`statusCode` must be 2XX, 4XX or 5XX.');
throw new UnscopedValidationError('`statusCode` must be 2XX, 4XX or 5XX.');
}

if (fixedResponse.messageBody && fixedResponse.messageBody.length > 1024) {
throw new Error('`messageBody` cannot have more than 1024 characters.');
throw new UnscopedValidationError('`messageBody` cannot have more than 1024 characters.');
}
}

Expand All @@ -408,10 +409,10 @@ function validateFixedResponse(fixedResponse: FixedResponse) {
*/
function validateRedirectResponse(redirectResponse: RedirectResponse) {
if (redirectResponse.protocol && !/^(HTTPS?|#\{protocol\})$/i.test(redirectResponse.protocol)) {
throw new Error('`protocol` must be HTTP, HTTPS, or #{protocol}.');
throw new UnscopedValidationError('`protocol` must be HTTP, HTTPS, or #{protocol}.');
}

if (!redirectResponse.statusCode || !/^HTTP_30[12]$/.test(redirectResponse.statusCode)) {
throw new Error('`statusCode` must be HTTP_301 or HTTP_302.');
throw new UnscopedValidationError('`statusCode` must be HTTP_301 or HTTP_302.');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ITrustStore } from './trust-store';
import * as ec2 from '../../../aws-ec2';
import * as cxschema from '../../../cloud-assembly-schema';
import { Duration, FeatureFlags, Lazy, Resource, Token } from '../../../core';
import { ValidationError } from '../../../core/lib/errors';
import * as cxapi from '../../../cx-api';
import { BaseListener, BaseListenerLookupOptions, IListener } from '../shared/base-listener';
import { HealthCheck } from '../shared/base-target-group';
Expand Down Expand Up @@ -197,7 +198,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
*/
public static fromLookup(scope: Construct, id: string, options: ApplicationListenerLookupOptions): IApplicationListener {
if (Token.isUnresolved(options.listenerArn)) {
throw new Error('All arguments to look up a load balancer listener must be concrete (no Tokens)');
throw new ValidationError('All arguments to look up a load balancer listener must be concrete (no Tokens)', scope);
}

let listenerProtocol: cxschema.LoadBalancerListenerProtocol | undefined;
Expand Down Expand Up @@ -251,10 +252,10 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
constructor(scope: Construct, id: string, props: ApplicationListenerProps) {
const [protocol, port] = determineProtocolAndPort(props.protocol, props.port);
if (protocol === undefined || port === undefined) {
throw new Error('At least one of \'port\' or \'protocol\' is required');
throw new ValidationError('At least one of \'port\' or \'protocol\' is required', scope);
}

validateMutualAuthentication(props.mutualAuthentication);
validateMutualAuthentication(scope, props.mutualAuthentication);

super(scope, id, {
loadBalancerArn: props.loadBalancer.loadBalancerArn,
Expand Down Expand Up @@ -290,7 +291,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
});

if (props.defaultAction && props.defaultTargetGroups) {
throw new Error('Specify at most one of \'defaultAction\' and \'defaultTargetGroups\'');
throw new ValidationError('Specify at most one of \'defaultAction\' and \'defaultTargetGroups\'', this);
}

if (props.defaultAction) {
Expand Down Expand Up @@ -361,7 +362,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
* default Action).
*/
public addAction(id: string, props: AddApplicationActionProps): void {
checkAddRuleProps(props);
checkAddRuleProps(this, props);

if (props.priority !== undefined) {
// New rule
Expand Down Expand Up @@ -389,7 +390,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
* become the default Action for this listener).
*/
public addTargetGroups(id: string, props: AddApplicationTargetGroupsProps): void {
checkAddRuleProps(props);
checkAddRuleProps(this, props);

if (props.priority !== undefined) {
// New rule
Expand Down Expand Up @@ -423,7 +424,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
public addTargets(id: string, props: AddApplicationTargetsProps): ApplicationTargetGroup {
if (!this.loadBalancer.vpc) {
// eslint-disable-next-line max-len
throw new Error('Can only call addTargets() when using a constructed Load Balancer or an imported Load Balancer with specified vpc; construct a new TargetGroup and use addTargetGroup');
throw new ValidationError('Can only call addTargets() when using a constructed Load Balancer or an imported Load Balancer with specified vpc; construct a new TargetGroup and use addTargetGroup', this);
}

const group = new ApplicationTargetGroup(this, id + 'Group', {
Expand All @@ -445,7 +446,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
* @deprecated Use `addAction()` instead
*/
public addFixedResponse(id: string, props: AddFixedResponseProps) {
checkAddRuleProps(props);
checkAddRuleProps(this, props);

const fixedResponse: FixedResponse = {
statusCode: props.statusCode,
Expand All @@ -459,11 +460,11 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
* Inlining the duplication functionality in v2 only (for now).
*/
if (fixedResponse.statusCode && !/^(2|4|5)\d\d$/.test(fixedResponse.statusCode)) {
throw new Error('`statusCode` must be 2XX, 4XX or 5XX.');
throw new ValidationError('`statusCode` must be 2XX, 4XX or 5XX.', this);
}

if (fixedResponse.messageBody && fixedResponse.messageBody.length > 1024) {
throw new Error('`messageBody` cannot have more than 1024 characters.');
throw new ValidationError('`messageBody` cannot have more than 1024 characters.', this);
}

if (props.priority) {
Expand All @@ -487,7 +488,7 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
* @deprecated Use `addAction()` instead
*/
public addRedirectResponse(id: string, props: AddRedirectResponseProps) {
checkAddRuleProps(props);
checkAddRuleProps(this, props);
const redirectResponse = {
host: props.host,
path: props.path,
Expand All @@ -503,11 +504,11 @@ export class ApplicationListener extends BaseListener implements IApplicationLis
* Inlining the duplication functionality in v2 only (for now).
*/
if (redirectResponse.protocol && !/^(HTTPS?|#\{protocol\})$/i.test(redirectResponse.protocol)) {
throw new Error('`protocol` must be HTTP, HTTPS, or #{protocol}.');
throw new ValidationError('`protocol` must be HTTP, HTTPS, or #{protocol}.', this);
}

if (!redirectResponse.statusCode || !/^HTTP_30[12]$/.test(redirectResponse.statusCode)) {
throw new Error('`statusCode` must be HTTP_301 or HTTP_302.');
throw new ValidationError('`statusCode` must be HTTP_301 or HTTP_302.', this);
}

if (props.priority) {
Expand Down Expand Up @@ -698,7 +699,7 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat
* At least one TargetGroup must be added without conditions.
*/
public addTargetGroups(id: string, props: AddApplicationTargetGroupsProps): void {
checkAddRuleProps(props);
checkAddRuleProps(this, props);

if (props.priority !== undefined) {
// New rule
Expand All @@ -708,7 +709,7 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat
...props,
});
} else {
throw new Error('Cannot add default Target Groups to imported ApplicationListener');
throw new ValidationError('Cannot add default Target Groups to imported ApplicationListener', this);
}
}

Expand All @@ -725,7 +726,7 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat
*/
public addTargets(_id: string, _props: AddApplicationTargetsProps): ApplicationTargetGroup {
// eslint-disable-next-line max-len
throw new Error('Can only call addTargets() when using a constructed ApplicationListener; construct a new TargetGroup and use addTargetGroup.');
throw new ValidationError('Can only call addTargets() when using a constructed ApplicationListener; construct a new TargetGroup and use addTargetGroup.', this);
}

/**
Expand All @@ -747,7 +748,7 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat
* property here to avoid having CloudFormation attempt to replace your resource.
*/
public addAction(id: string, props: AddApplicationActionProps): void {
checkAddRuleProps(props);
checkAddRuleProps(this, props);

if (props.priority !== undefined) {
const ruleId = props.removeSuffix ? id : id + 'Rule';
Expand All @@ -760,7 +761,7 @@ abstract class ExternalApplicationListener extends Resource implements IApplicat
...props,
});
} else {
throw new Error('priority must be set for actions added to an imported listener');
throw new ValidationError('priority must be set for actions added to an imported listener', this);
}
}
}
Expand Down Expand Up @@ -1036,17 +1037,17 @@ export interface AddFixedResponseProps extends AddRuleProps, FixedResponse {
export interface AddRedirectResponseProps extends AddRuleProps, RedirectResponse {
}

function checkAddRuleProps(props: AddRuleProps) {
function checkAddRuleProps(scope: Construct, props: AddRuleProps) {
const conditionsCount = props.conditions?.length || 0;
const hasAnyConditions = conditionsCount !== 0 ||
props.hostHeader !== undefined || props.pathPattern !== undefined || props.pathPatterns !== undefined;
const hasPriority = props.priority !== undefined;
if (hasAnyConditions !== hasPriority) {
throw new Error('Setting \'conditions\', \'pathPattern\' or \'hostHeader\' also requires \'priority\', and vice versa');
throw new ValidationError('Setting \'conditions\', \'pathPattern\' or \'hostHeader\' also requires \'priority\', and vice versa', scope);
}
}

function validateMutualAuthentication(mutualAuthentication?: MutualAuthentication): void {
function validateMutualAuthentication(scope: Construct, mutualAuthentication?: MutualAuthentication): void {
if (!mutualAuthentication) {
return;
}
Expand All @@ -1055,17 +1056,17 @@ function validateMutualAuthentication(mutualAuthentication?: MutualAuthenticatio

if (currentMode === MutualAuthenticationMode.VERIFY) {
if (!mutualAuthentication.trustStore) {
throw new Error(`You must set 'trustStore' when 'mode' is '${MutualAuthenticationMode.VERIFY}'`);
throw new ValidationError(`You must set 'trustStore' when 'mode' is '${MutualAuthenticationMode.VERIFY}'`, scope);
}
}

if (currentMode === MutualAuthenticationMode.OFF || currentMode === MutualAuthenticationMode.PASS_THROUGH) {
if (mutualAuthentication.trustStore) {
throw new Error(`You cannot set 'trustStore' when 'mode' is '${MutualAuthenticationMode.OFF}' or '${MutualAuthenticationMode.PASS_THROUGH}'`);
throw new ValidationError(`You cannot set 'trustStore' when 'mode' is '${MutualAuthenticationMode.OFF}' or '${MutualAuthenticationMode.PASS_THROUGH}'`, scope);
}

if (mutualAuthentication.ignoreClientCertificateExpiry !== undefined) {
throw new Error(`You cannot set 'ignoreClientCertificateExpiry' when 'mode' is '${MutualAuthenticationMode.OFF}' or '${MutualAuthenticationMode.PASS_THROUGH}'`);
throw new ValidationError(`You cannot set 'ignoreClientCertificateExpiry' when 'mode' is '${MutualAuthenticationMode.OFF}' or '${MutualAuthenticationMode.PASS_THROUGH}'`, scope);
}
}
}
Loading
Loading