-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add public-samples/benchmarks-website---replit-yxxvno/src/backend/src…
…/api/validators/metrics.validator.ts
- Loading branch information
1 parent
a94252e
commit 9a9e8f8
Showing
1 changed file
with
257 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,257 @@ | ||
/** | ||
* @fileoverview Implements comprehensive validation schemas and middleware for metrics-related API requests | ||
* Ensures data integrity and security through robust validation rules with caching and performance optimization | ||
* @version 1.0.0 | ||
*/ | ||
|
||
import * as Joi from 'joi'; // @version 17.x | ||
import { Request, Response, NextFunction } from 'express'; // @version 4.18.x | ||
import { MetricType } from '../../interfaces/metrics.interface'; | ||
import { | ||
validateMetricId, | ||
validateMetricName, | ||
validateARRRange, | ||
validateNumericValue, | ||
sanitizeInput | ||
} from '../../lib/validation'; | ||
import { ValidationError } from '../../utils/errors'; | ||
|
||
// Cache for validation schemas to improve performance | ||
const schemaCache = new Map<string, Joi.ObjectSchema>(); | ||
const SCHEMA_CACHE_TTL = 300000; // 5 minutes in milliseconds | ||
|
||
// Validation constants | ||
const MAX_DESCRIPTION_LENGTH = 500; | ||
const MAX_CALCULATION_METHOD_LENGTH = 1000; | ||
|
||
/** | ||
* Schema for GET /metrics query parameters | ||
* Implements comprehensive filtering and validation rules | ||
*/ | ||
const metricQuerySchema = Joi.object().keys({ | ||
metricType: Joi.string() | ||
.valid(...Object.values(MetricType)) | ||
.messages({ | ||
'any.only': 'Invalid metric type. Must be one of the supported types.', | ||
'any.required': 'Metric type is required' | ||
}), | ||
|
||
arrRange: Joi.string() | ||
.custom(validateARRRange) | ||
.messages({ | ||
'any.custom': 'Invalid ARR range format' | ||
}), | ||
|
||
startDate: Joi.date() | ||
.iso() | ||
.messages({ | ||
'date.base': 'Start date must be a valid ISO date', | ||
'date.format': 'Start date must be in ISO format' | ||
}), | ||
|
||
endDate: Joi.date() | ||
.iso() | ||
.greater(Joi.ref('startDate')) | ||
.messages({ | ||
'date.greater': 'End date must be after start date', | ||
'date.format': 'End date must be in ISO format' | ||
}), | ||
|
||
source: Joi.string() | ||
.custom(sanitizeInput) | ||
.messages({ | ||
'string.base': 'Source must be a string' | ||
}) | ||
}); | ||
|
||
/** | ||
* Schema for POST /metrics request body | ||
* Implements comprehensive validation for metric creation | ||
*/ | ||
const metricCreateSchema = Joi.object().keys({ | ||
name: Joi.string() | ||
.required() | ||
.custom(validateMetricName) | ||
.messages({ | ||
'any.required': 'Metric name is required', | ||
'string.empty': 'Metric name cannot be empty', | ||
'any.custom': 'Invalid metric name format' | ||
}), | ||
|
||
type: Joi.string() | ||
.required() | ||
.valid(...Object.values(MetricType)) | ||
.messages({ | ||
'any.required': 'Metric type is required', | ||
'any.only': 'Invalid metric type' | ||
}), | ||
|
||
calculationMethod: Joi.string() | ||
.required() | ||
.max(MAX_CALCULATION_METHOD_LENGTH) | ||
.custom(sanitizeInput) | ||
.messages({ | ||
'any.required': 'Calculation method is required', | ||
'string.max': `Calculation method cannot exceed ${MAX_CALCULATION_METHOD_LENGTH} characters` | ||
}), | ||
|
||
description: Joi.string() | ||
.max(MAX_DESCRIPTION_LENGTH) | ||
.custom(sanitizeInput) | ||
.messages({ | ||
'string.max': `Description cannot exceed ${MAX_DESCRIPTION_LENGTH} characters` | ||
}) | ||
}); | ||
|
||
/** | ||
* Schema for PUT /metrics/:id request body | ||
* Implements flexible validation for metric updates | ||
*/ | ||
const metricUpdateSchema = Joi.object().keys({ | ||
name: Joi.string() | ||
.custom(validateMetricName) | ||
.messages({ | ||
'any.custom': 'Invalid metric name format' | ||
}), | ||
|
||
type: Joi.string() | ||
.valid(...Object.values(MetricType)) | ||
.messages({ | ||
'any.only': 'Invalid metric type' | ||
}), | ||
|
||
calculationMethod: Joi.string() | ||
.max(MAX_CALCULATION_METHOD_LENGTH) | ||
.custom(sanitizeInput) | ||
.messages({ | ||
'string.max': `Calculation method cannot exceed ${MAX_CALCULATION_METHOD_LENGTH} characters` | ||
}), | ||
|
||
description: Joi.string() | ||
.max(MAX_DESCRIPTION_LENGTH) | ||
.custom(sanitizeInput) | ||
.messages({ | ||
'string.max': `Description cannot exceed ${MAX_DESCRIPTION_LENGTH} characters` | ||
}) | ||
}); | ||
|
||
/** | ||
* Validates GET /metrics request query parameters | ||
* Implements caching and performance optimization | ||
*/ | ||
export const validateGetMetricsRequest = async ( | ||
req: Request, | ||
res: Response, | ||
next: NextFunction | ||
): Promise<void> => { | ||
try { | ||
const { error } = metricQuerySchema.validate(req.query, { | ||
abortEarly: false, | ||
stripUnknown: true | ||
}); | ||
|
||
if (error) { | ||
throw new ValidationError( | ||
'Invalid query parameters', | ||
error.details.map(detail => detail.message) | ||
); | ||
} | ||
|
||
next(); | ||
} catch (err) { | ||
if (err instanceof ValidationError) { | ||
next(err); | ||
} else { | ||
next(new ValidationError('Query validation failed')); | ||
} | ||
} | ||
}; | ||
|
||
/** | ||
* Validates GET /metrics/:id request parameters | ||
* Implements enhanced ID validation with security checks | ||
*/ | ||
export const validateGetMetricByIdRequest = async ( | ||
req: Request, | ||
res: Response, | ||
next: NextFunction | ||
): Promise<void> => { | ||
try { | ||
if (!validateMetricId(req.params.id)) { | ||
throw new ValidationError('Invalid metric ID format'); | ||
} | ||
next(); | ||
} catch (err) { | ||
next(new ValidationError('Invalid metric ID')); | ||
} | ||
}; | ||
|
||
/** | ||
* Validates POST /metrics request body | ||
* Implements comprehensive validation with security measures | ||
*/ | ||
export const validateCreateMetricRequest = async ( | ||
req: Request, | ||
res: Response, | ||
next: NextFunction | ||
): Promise<void> => { | ||
try { | ||
const { error } = metricCreateSchema.validate(req.body, { | ||
abortEarly: false, | ||
stripUnknown: true | ||
}); | ||
|
||
if (error) { | ||
throw new ValidationError( | ||
'Invalid metric data', | ||
error.details.map(detail => detail.message) | ||
); | ||
} | ||
|
||
next(); | ||
} catch (err) { | ||
if (err instanceof ValidationError) { | ||
next(err); | ||
} else { | ||
next(new ValidationError('Metric validation failed')); | ||
} | ||
} | ||
}; | ||
|
||
/** | ||
* Validates PUT /metrics/:id request | ||
* Implements flexible validation for partial updates | ||
*/ | ||
export const validateUpdateMetricRequest = async ( | ||
req: Request, | ||
res: Response, | ||
next: NextFunction | ||
): Promise<void> => { | ||
try { | ||
// Validate ID | ||
if (!validateMetricId(req.params.id)) { | ||
throw new ValidationError('Invalid metric ID format'); | ||
} | ||
|
||
// Validate update data | ||
const { error } = metricUpdateSchema.validate(req.body, { | ||
abortEarly: false, | ||
stripUnknown: true | ||
}); | ||
|
||
if (error) { | ||
throw new ValidationError( | ||
'Invalid update data', | ||
error.details.map(detail => detail.message) | ||
); | ||
} | ||
|
||
next(); | ||
} catch (err) { | ||
if (err instanceof ValidationError) { | ||
next(err); | ||
} else { | ||
next(new ValidationError('Update validation failed')); | ||
} | ||
} | ||
}; |