Use Custom Error Classes for Validation
This commit is contained in:
@@ -20,3 +20,43 @@ export class ExponentialBackoffStoppedRetriesError extends Error {
|
|||||||
this.name = 'ExponentialBackoffStoppedRetriesError';
|
this.name = 'ExponentialBackoffStoppedRetriesError';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when an exponential backoff option is too small
|
||||||
|
*/
|
||||||
|
export class ExponentialBackoffNumberTooSmallError extends Error {
|
||||||
|
constructor(option: string, value: number, min: number) {
|
||||||
|
super(`Exponential backoff option "${option}" is too small. Must be at least ${min}`);
|
||||||
|
this.name = 'ExponentialBackoffNumberTooSmallError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when an exponential backoff option is out of bounds
|
||||||
|
*/
|
||||||
|
export class ExponentialBackoffNumberOutOfBoundsError extends Error {
|
||||||
|
constructor(option: string, value: number, min: number, max: number) {
|
||||||
|
super(`Exponential backoff option "${option}" is out of bounds. Must be between ${min} and ${max}`);
|
||||||
|
this.name = 'ExponentialBackoffNumberOutOfBoundsError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when an exponential backoff option is an invalid infinite integer
|
||||||
|
*/
|
||||||
|
export class ExponentialBackoffInvalidInfiniteIntegerError extends Error {
|
||||||
|
constructor(option: string) {
|
||||||
|
super(`Exponential backoff option "${option}" is invalid. Must be a finite number`);
|
||||||
|
this.name = 'ExponentialBackoffInvalidInfiniteIntegerError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when an exponential backoff option is not an integer
|
||||||
|
*/
|
||||||
|
export class ExponentialBackoffNonIntegerError extends Error {
|
||||||
|
constructor(option: string) {
|
||||||
|
super(`Exponential backoff option "${option}" is invalid. Must be an integer`);
|
||||||
|
this.name = 'ExponentialBackoffNonIntegerError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { ExponentialBackoffStoppedRetriesError, ExponentialBackoffMaxRetriesHitError } from './errors.ts';
|
import {
|
||||||
|
ExponentialBackoffStoppedRetriesError,
|
||||||
|
ExponentialBackoffMaxRetriesHitError,
|
||||||
|
ExponentialBackoffInvalidInfiniteIntegerError,
|
||||||
|
ExponentialBackoffNonIntegerError,
|
||||||
|
ExponentialBackoffNumberTooSmallError,
|
||||||
|
ExponentialBackoffNumberOutOfBoundsError,
|
||||||
|
} from './errors.ts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exponential backoff is a technique used to retry a function after a delay.
|
* Exponential backoff is a technique used to retry a function after a delay.
|
||||||
@@ -106,50 +113,57 @@ export class ExponentialBackoff {
|
|||||||
* @throws An error if the options are invalid
|
* @throws An error if the options are invalid
|
||||||
*/
|
*/
|
||||||
public static validateOptions(options: ExponentialBackoffOptions): void {
|
public static validateOptions(options: ExponentialBackoffOptions): void {
|
||||||
// Validate the max delay is a finite number not less than 0
|
/** Validate the value is finite, throwing an {@link ExponentialBackoffInvalidInfiniteIntegerError} if the value is infinite */
|
||||||
if (!Number.isFinite(options.maxDelay)) {
|
const isFinite = (key: string, value: number): void => {
|
||||||
throw new Error('maxDelay must be a finite number');
|
if (!Number.isFinite(value)) {
|
||||||
|
throw new ExponentialBackoffInvalidInfiniteIntegerError(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Validate the value is an integer, throwing a {@link ExponentialBackoffNonIntegerError} if it is not an integer */
|
||||||
|
const isInteger = (key: string, value: number): void => {
|
||||||
|
if (!Number.isInteger(value)) {
|
||||||
|
throw new ExponentialBackoffNonIntegerError(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Validate the value is within the bounds, throwing a {@link ExponentialBackoffNumberOutOfBoundsError} if it is not within the bounds */
|
||||||
|
const isWithinBounds = (key: string, value: number, min: number, max?: number): void => {
|
||||||
|
// If both the min and max are defined, validate the value, throwing a number out of bounds error if it is not within the bounds
|
||||||
|
if (min !== undefined && max !== undefined) {
|
||||||
|
if (value < min || value > max) {
|
||||||
|
throw new ExponentialBackoffNumberOutOfBoundsError(key, value, min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.maxDelay < 0) {
|
return;
|
||||||
throw new Error('maxDelay must be not less than 0');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the max attempts is a finite number not less than 0
|
// If only the min is defined, validate the value, throwing a number too small error if it is less than the min
|
||||||
if (!Number.isFinite(options.maxAttempts)) {
|
if (value < min) {
|
||||||
throw new Error('maxAttempts must be a finite number');
|
throw new ExponentialBackoffNumberTooSmallError(key, value, min);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (options.maxAttempts < 0) {
|
// Validate the max delay
|
||||||
throw new Error('maxAttempts must be not less than 0');
|
isFinite('maxDelay', options.maxDelay);
|
||||||
}
|
isWithinBounds('maxDelay', options.maxDelay, 0);
|
||||||
|
|
||||||
// Validate the base delay is a finite number not less than 0
|
// Validate the max attempts
|
||||||
if (!Number.isFinite(options.baseDelay)) {
|
isFinite('maxAttempts', options.maxAttempts);
|
||||||
throw new Error('baseDelay must be a finite number');
|
isInteger('maxAttempts', options.maxAttempts);
|
||||||
}
|
isWithinBounds('maxAttempts', options.maxAttempts, 0);
|
||||||
|
|
||||||
if (options.baseDelay < 0) {
|
// Validate the base delay
|
||||||
throw new Error('baseDelay must be not less than 0');
|
isFinite('baseDelay', options.baseDelay);
|
||||||
}
|
isWithinBounds('baseDelay', options.baseDelay, 0);
|
||||||
|
|
||||||
// Validate the growth rate is a finite number not less than 0
|
// Validate the growth rate
|
||||||
if (!Number.isFinite(options.growthRate)) {
|
isFinite('growthRate', options.growthRate);
|
||||||
throw new Error('growthRate must be a finite number');
|
isWithinBounds('growthRate', options.growthRate, 0);
|
||||||
}
|
|
||||||
|
|
||||||
if (options.growthRate < 0) {
|
// Validate the jitter
|
||||||
throw new Error('growthRate must be not less than 0');
|
isFinite('jitter', options.jitter);
|
||||||
}
|
isWithinBounds('jitter', options.jitter, 0, 1);
|
||||||
|
|
||||||
// Validate the jitter is a finite number not less than 0 or greater than 1
|
|
||||||
if (!Number.isFinite(options.jitter)) {
|
|
||||||
throw new Error('jitter must be a finite number');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.jitter < 0 || options.jitter > 1) {
|
|
||||||
throw new Error('jitter must be not less than 0 or greater than 1');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -498,7 +498,7 @@ const testExponentialBackoffValidateOptionsRejectsNegativeValues = (): void => {
|
|||||||
ExponentialBackoff.validateOptions({
|
ExponentialBackoff.validateOptions({
|
||||||
...validExponentialBackoffOptions,
|
...validExponentialBackoffOptions,
|
||||||
[field]: value,
|
[field]: value,
|
||||||
})).toThrow(`${field} must be not less than 0`);
|
})).toThrow(`Exponential backoff option "${field}" is too small. Must be at least 0`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -515,7 +515,7 @@ const testExponentialBackoffValidateOptionsRejectsInvalidJitter = (): void => {
|
|||||||
ExponentialBackoff.validateOptions({
|
ExponentialBackoff.validateOptions({
|
||||||
...validExponentialBackoffOptions,
|
...validExponentialBackoffOptions,
|
||||||
jitter: value,
|
jitter: value,
|
||||||
})).toThrow('jitter must be not less than 0 or greater than 1');
|
})).toThrow('Exponential backoff option "jitter" is out of bounds. Must be between 0 and 1');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -538,7 +538,24 @@ const testExponentialBackoffValidateOptionsRejectsNonFiniteValues = (): void =>
|
|||||||
ExponentialBackoff.validateOptions({
|
ExponentialBackoff.validateOptions({
|
||||||
...validExponentialBackoffOptions,
|
...validExponentialBackoffOptions,
|
||||||
[field]: value,
|
[field]: value,
|
||||||
})).toThrow(`${field} must be a finite number`);
|
})).toThrow(`Exponential backoff option "${field}" is invalid. Must be a finite number`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that {@link ExponentialBackoff.validateOptions} rejects non-integer values.
|
||||||
|
*/
|
||||||
|
const testExponentialBackoffValidateOptionsRejectsNonIntegerValues = (): void => {
|
||||||
|
// Define our test cases with each value being a non-integer
|
||||||
|
const nonIntegerCases = [{ field: 'maxAttempts', value: 1.5 }] as const;
|
||||||
|
|
||||||
|
// Iterate through the test cases and expect an error to be thrown
|
||||||
|
for (const { field, value } of nonIntegerCases) {
|
||||||
|
expect(() =>
|
||||||
|
ExponentialBackoff.validateOptions({
|
||||||
|
...validExponentialBackoffOptions,
|
||||||
|
[field]: value,
|
||||||
|
})).toThrow(`Exponential backoff option "${field}" is invalid. Must be an integer`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -561,7 +578,7 @@ const testExponentialBackoffValidateOptionsRejectsNaN = (): void => {
|
|||||||
ExponentialBackoff.validateOptions({
|
ExponentialBackoff.validateOptions({
|
||||||
...validExponentialBackoffOptions,
|
...validExponentialBackoffOptions,
|
||||||
[field]: value,
|
[field]: value,
|
||||||
})).toThrow(`${field} must be a finite number`);
|
})).toThrow(`Exponential backoff option "${field}" is invalid. Must be a finite number`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -589,6 +606,7 @@ const runTests = async (): Promise<void> => {
|
|||||||
test('ExponentialBackoff.validateOptions: rejects negative values', testExponentialBackoffValidateOptionsRejectsNegativeValues);
|
test('ExponentialBackoff.validateOptions: rejects negative values', testExponentialBackoffValidateOptionsRejectsNegativeValues);
|
||||||
test('ExponentialBackoff.validateOptions: rejects invalid jitter', testExponentialBackoffValidateOptionsRejectsInvalidJitter);
|
test('ExponentialBackoff.validateOptions: rejects invalid jitter', testExponentialBackoffValidateOptionsRejectsInvalidJitter);
|
||||||
test('ExponentialBackoff.validateOptions: rejects Infinity', testExponentialBackoffValidateOptionsRejectsNonFiniteValues);
|
test('ExponentialBackoff.validateOptions: rejects Infinity', testExponentialBackoffValidateOptionsRejectsNonFiniteValues);
|
||||||
|
test('ExponentialBackoff.validateOptions: rejects non-integer values', testExponentialBackoffValidateOptionsRejectsNonIntegerValues);
|
||||||
test('ExponentialBackoff.validateOptions: rejects NaN', testExponentialBackoffValidateOptionsRejectsNaN);
|
test('ExponentialBackoff.validateOptions: rejects NaN', testExponentialBackoffValidateOptionsRejectsNaN);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user