Skip to content

Commit

Permalink
change validator from class to static functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Mark Kuhn committed Sep 18, 2022
1 parent a9960ee commit 1734f9f
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 143 deletions.
8 changes: 5 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@
"license": "Apache-2.0",
"dependencies": {
"@datastructures-js/heap": "^4.0.2",
"@types/validator": "^13.7.5",
"validator": "^13.7.0"
},
"devDependencies": {
"@faker-js/faker": "^7.5.0",
"@types/faker": "^4.1.5",
"@types/jest": "^26.0.22",
"@types/node": "^12.0.8",
"@types/validator": "^13.7.6",
"@typescript-eslint/eslint-plugin": "^5.36.1",
"@typescript-eslint/parser": "^5.36.1",
"aws-sdk": "^2.551.0",
Expand Down
12 changes: 6 additions & 6 deletions src/logger/MetricsContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import Configuration from '../config/Configuration';
import { LOG } from '../utils/Logger';
import { Validator } from '../utils/Validator';
import { validateNamespace, validateTimestamp, validateDimensionSet, validateMetric } from '../utils/Validator';
import { MetricValues } from './MetricValues';
import { Unit } from './Unit';

Expand Down Expand Up @@ -83,7 +83,7 @@ export class MetricsContext {
}

public setNamespace(value: string): void {
Validator.validateNamespace(value);
validateNamespace(value);
this.namespace = value;
}

Expand All @@ -92,7 +92,7 @@ export class MetricsContext {
}

public setTimestamp(timestamp: Date | number): void {
Validator.validateTimestamp(timestamp);
validateTimestamp(timestamp);
this.timestamp = timestamp;
this.meta.Timestamp = MetricsContext.resolveMetaTimestamp(timestamp);
}
Expand All @@ -114,7 +114,7 @@ export class MetricsContext {
* @param dimensions
*/
public putDimensions(incomingDimensionSet: Record<string, string>): void {
Validator.validateDimensionSet(incomingDimensionSet);
validateDimensionSet(incomingDimensionSet);

// Duplicate dimensions sets are removed before being added to the end of the collection.
// This ensures the latest dimension key-value is used as a target member on the root EMF node.
Expand All @@ -139,7 +139,7 @@ export class MetricsContext {
* @param dimensionSets
*/
public setDimensions(dimensionSets: Array<Record<string, string>>, useDefault = false): void {
dimensionSets.forEach((dimensionSet) => Validator.validateDimensionSet(dimensionSet));
dimensionSets.forEach((dimensionSet) => validateDimensionSet(dimensionSet));
this.shouldUseDefaultDimensions = useDefault;
this.dimensions = dimensionSets;
}
Expand Down Expand Up @@ -181,7 +181,7 @@ export class MetricsContext {
}

public putMetric(key: string, value: number, unit?: Unit | string): void {
Validator.validateMetric(key, value, unit);
validateMetric(key, value, unit);

const currentMetric = this.metrics.get(key);
if (currentMetric) {
Expand Down
264 changes: 131 additions & 133 deletions src/utils/Validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,161 +22,159 @@ import { InvalidMetricError } from '../exceptions/InvalidMetricError';
import { InvalidNamespaceError } from '../exceptions/InvalidNamespaceError';
import { InvalidTimestampError } from '../exceptions/InvalidTimestampError';

export class Validator {
/**
* Validates dimension set.
* @see [CloudWatch Dimensions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_Dimension.html)
*
* @param dimensionSet
* @throws {DimensionSetExceededError} Dimension set must not exceed 30 dimensions.
* @throws {InvalidDimensionError} Dimension name and value must be valid.
*/
public static validateDimensionSet(dimensionSet: Record<string, string>): void {
// Validates dimension set length
if (Object.keys(dimensionSet).length > Constants.MAX_DIMENSION_SET_SIZE)
throw new DimensionSetExceededError(
`Maximum number of dimensions per dimension set allowed are ${Constants.MAX_DIMENSION_SET_SIZE}`,
);
/**
* Validates dimension set.
* @see [CloudWatch Dimensions](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_Dimension.html)
*
* @param dimensionSet
* @throws {DimensionSetExceededError} Dimension set must not exceed 30 dimensions.
* @throws {InvalidDimensionError} Dimension name and value must be valid.
*/
const validateDimensionSet = (dimensionSet: Record<string, string>): void => {
// Validates dimension set length
if (Object.keys(dimensionSet).length > Constants.MAX_DIMENSION_SET_SIZE)
throw new DimensionSetExceededError(
`Maximum number of dimensions per dimension set allowed are ${Constants.MAX_DIMENSION_SET_SIZE}`,
);

// Validate dimension key and value
Object.entries(dimensionSet).forEach(([key, value]) => {
dimensionSet[key] = value = String(value);

if (!validator.isAscii(key)) {
throw new InvalidDimensionError(`Dimension key ${key} has invalid characters`);
}
if (!validator.isAscii(value)) {
throw new InvalidDimensionError(`Dimension value ${value} has invalid characters`);
}

if (key.trim().length == 0) {
throw new InvalidDimensionError(`Dimension key ${key} must include at least one non-whitespace character`);
}

if (value.trim().length == 0) {
throw new InvalidDimensionError(`Dimension value ${value} must include at least one non-whitespace character`);
}

if (key.length > Constants.MAX_DIMENSION_NAME_LENGTH) {
throw new InvalidDimensionError(
`Dimension key ${key} must not exceed maximum length ${Constants.MAX_DIMENSION_NAME_LENGTH}`,
);
}

if (value.length > Constants.MAX_DIMENSION_VALUE_LENGTH) {
throw new InvalidDimensionError(
`Dimension value ${value} must not exceed maximum length ${Constants.MAX_DIMENSION_VALUE_LENGTH}`,
);
}

if (key.startsWith(':')) {
throw new InvalidDimensionError(`Dimension key ${key} cannot start with ':'`);
}
});
}
// Validate dimension key and value
Object.entries(dimensionSet).forEach(([key, value]) => {
dimensionSet[key] = value = String(value);

/**
* Validates metric.
* @see [CloudWatch Metric](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html)
*
* @param key
* @param value
*
* @throws {InvalidMetricError} Metric name must be valid.
*/
public static validateMetric(key: string, value: number, unit?: Unit | string): void {
if (key.trim().length == 0) {
throw new InvalidMetricError(`Metric key ${key} must include at least one non-whitespace character`);
if (!validator.isAscii(key)) {
throw new InvalidDimensionError(`Dimension key ${key} has invalid characters`);
}
if (!validator.isAscii(value)) {
throw new InvalidDimensionError(`Dimension value ${value} has invalid characters`);
}

if (key.length > Constants.MAX_METRIC_NAME_LENGTH) {
throw new InvalidMetricError(
`Metric key ${key} must not exceed maximum length ${Constants.MAX_METRIC_NAME_LENGTH}`,
);
if (key.trim().length == 0) {
throw new InvalidDimensionError(`Dimension key ${key} must include at least one non-whitespace character`);
}

if (!Number.isFinite(value)) {
throw new InvalidMetricError(`Metric value ${value} is not a number`);
if (value.trim().length == 0) {
throw new InvalidDimensionError(`Dimension value ${value} must include at least one non-whitespace character`);
}

if (value > Number.MAX_SAFE_INTEGER) {
throw new InvalidMetricError(
`Metric value ${value} must not exceed maximum value ${Number.MAX_SAFE_INTEGER}}`,
if (key.length > Constants.MAX_DIMENSION_NAME_LENGTH) {
throw new InvalidDimensionError(
`Dimension key ${key} must not exceed maximum length ${Constants.MAX_DIMENSION_NAME_LENGTH}`,
);
}

if (value < -Number.MAX_SAFE_INTEGER) {
throw new InvalidMetricError(
`Metric value ${value} must not be less than minimum value ${-Number.MAX_SAFE_INTEGER}`,
if (value.length > Constants.MAX_DIMENSION_VALUE_LENGTH) {
throw new InvalidDimensionError(
`Dimension value ${value} must not exceed maximum length ${Constants.MAX_DIMENSION_VALUE_LENGTH}`,
);
}

if (
unit !== undefined &&
!Object.values(Unit)
.map(u => String(u))
.includes(unit)
) {
throw new InvalidMetricError(`Metric unit ${unit} is not valid`);
if (key.startsWith(':')) {
throw new InvalidDimensionError(`Dimension key ${key} cannot start with ':'`);
}
}
});
};

/**
* Validates metric namespace.
* @see [CloudWatch Namespace](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Namespace)
*
* @param namespace
* @throws {InvalidNamespaceError} Namespace must be of valid length.
*/
public static validateNamespace(namespace: string): void {
if (namespace.trim().length == 0) {
throw new InvalidNamespaceError(`Namespace must include at least one non-whitespace character`);
}
/**
* Validates metric.
* @see [CloudWatch Metric](https://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_MetricDatum.html)
*
* @param key
* @param value
*
* @throws {InvalidMetricError} Metric name must be valid.
*/
const validateMetric = (key: string, value: number, unit?: Unit | string): void => {
if (key.trim().length == 0) {
throw new InvalidMetricError(`Metric key ${key} must include at least one non-whitespace character`);
}

if (namespace.length > Constants.MAX_NAMESPACE_LENGTH) {
throw new InvalidNamespaceError(`Namespace must not exceed maximum length ${Constants.MAX_NAMESPACE_LENGTH}`);
}
if (key.length > Constants.MAX_METRIC_NAME_LENGTH) {
throw new InvalidMetricError(
`Metric key ${key} must not exceed maximum length ${Constants.MAX_METRIC_NAME_LENGTH}`,
);
}

if (!validator.matches(namespace, Constants.VALID_NAMESPACE_REGEX)) {
throw new InvalidNamespaceError(`Namespace ${namespace} has invalid characters`);
}
if (!Number.isFinite(value)) {
throw new InvalidMetricError(`Metric value ${value} is not a number`);
}

/**
* Validates timestamp.
* @see [CloudWatch Timestamp](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#about_timestamp)
*
* @param timestamp
*/
public static validateTimestamp(timestamp: Date | number): void {
timestamp = timestamp instanceof Date ? timestamp : new Date(timestamp);

let timestampStr;
try {
timestampStr = timestamp.toISOString();
} catch (e) {
throw new InvalidTimestampError(`Timestamp ${String(timestamp)} is invalid`);
}
if (value > Number.MAX_SAFE_INTEGER) {
throw new InvalidMetricError(`Metric value ${value} must not exceed maximum value ${Number.MAX_SAFE_INTEGER}}`);
}

const isTooOld = validator.isBefore(
timestampStr,
new Date(Date.now() - Constants.MAX_TIMESTAMP_PAST_AGE).toISOString(),
);
const isTooNew = validator.isAfter(
timestampStr,
new Date(Date.now() + Constants.MAX_TIMESTAMP_FUTURE_AGE).toISOString(),
if (value < -Number.MAX_SAFE_INTEGER) {
throw new InvalidMetricError(
`Metric value ${value} must not be less than minimum value ${-Number.MAX_SAFE_INTEGER}`,
);
}

if (isTooOld) {
throw new InvalidTimestampError(
`Timestamp ${timestampStr} must not be older than ${Constants.MAX_TIMESTAMP_PAST_AGE} milliseconds`,
);
}
if (
unit !== undefined &&
!Object.values(Unit)
.map((u) => String(u))
.includes(unit)
) {
throw new InvalidMetricError(`Metric unit ${unit} is not valid`);
}
};

if (isTooNew) {
throw new InvalidTimestampError(
`Timestamp ${timestampStr} must not be newer than ${Constants.MAX_TIMESTAMP_FUTURE_AGE} milliseconds`,
);
}
/**
* Validates metric namespace.
* @see [CloudWatch Namespace](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#Namespace)
*
* @param namespace
* @throws {InvalidNamespaceError} Namespace must be of valid length.
*/
const validateNamespace = (namespace: string): void => {
if (namespace.trim().length == 0) {
throw new InvalidNamespaceError(`Namespace must include at least one non-whitespace character`);
}

if (namespace.length > Constants.MAX_NAMESPACE_LENGTH) {
throw new InvalidNamespaceError(`Namespace must not exceed maximum length ${Constants.MAX_NAMESPACE_LENGTH}`);
}

if (!validator.matches(namespace, Constants.VALID_NAMESPACE_REGEX)) {
throw new InvalidNamespaceError(`Namespace ${namespace} has invalid characters`);
}
}
};

/**
* Validates timestamp.
* @see [CloudWatch Timestamp](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html#about_timestamp)
*
* @param timestamp
*/
const validateTimestamp = (timestamp: Date | number): void => {
timestamp = timestamp instanceof Date ? timestamp : new Date(timestamp);

let timestampStr;
try {
timestampStr = timestamp.toISOString();
} catch (e) {
throw new InvalidTimestampError(`Timestamp ${String(timestamp)} is invalid`);
}

const isTooOld = validator.isBefore(
timestampStr,
new Date(Date.now() - Constants.MAX_TIMESTAMP_PAST_AGE).toISOString(),
);
const isTooNew = validator.isAfter(
timestampStr,
new Date(Date.now() + Constants.MAX_TIMESTAMP_FUTURE_AGE).toISOString(),
);

if (isTooOld) {
throw new InvalidTimestampError(
`Timestamp ${timestampStr} must not be older than ${Constants.MAX_TIMESTAMP_PAST_AGE} milliseconds`,
);
}

if (isTooNew) {
throw new InvalidTimestampError(
`Timestamp ${timestampStr} must not be newer than ${Constants.MAX_TIMESTAMP_FUTURE_AGE} milliseconds`,
);
}
};

export { validateDimensionSet, validateMetric, validateNamespace, validateTimestamp };

0 comments on commit 1734f9f

Please sign in to comment.