Files
market_screener/server/domains/shared/utils/sanitizer.ts
T
Sai Kiran Vella 96a752ecf7 phase-9: domain-driven architecture complete
- Restructured server layer with 5 domains: shared, screener, portfolio, calls, finance
- Migrated 58 TypeScript files to domain-driven structure
- Updated CLAUDE.md with new architecture documentation
- Added .gitignore rules for .md files (except CLAUDE.md)
- Removed unused CatalystAnalyst import from app.ts
- Fixed lint errors: removed unused imports, fixed regex escape, added console suppressions
- Verified no sensitive data in git history
- Server code compiles cleanly with TypeScript strict mode
2026-06-06 18:18:22 -04:00

143 lines
3.8 KiB
TypeScript

/**
* Sanitize a ticker symbol.
* - Converts to uppercase
* - Trims whitespace
* - Validates non-empty
*
* @param ticker The ticker symbol (e.g. "aapl", " MSFT ", "BRK.B")
* @returns Normalized ticker (e.g. "AAPL", "MSFT", "BRK.B")
* @throws Error if ticker is empty or invalid
*/
export function sanitizeTicker(ticker: string): string {
if (!ticker || typeof ticker !== 'string') {
throw new Error('Invalid ticker: must be a non-empty string');
}
const normalized = ticker.trim().toUpperCase();
if (!normalized) {
throw new Error('Invalid ticker: cannot be empty or whitespace');
}
// Optional: validate ticker format (alphanumeric + dots/hyphens)
if (!/^[A-Z0-9-.]+$/.test(normalized)) {
throw new Error(`Invalid ticker format: ${normalized}`);
}
return normalized;
}
/**
* Sanitize an array of tickers.
*
* @param tickers Array of ticker symbols
* @returns Array of normalized tickers
* @throws Error if any ticker is invalid
*/
export function sanitizeTickers(tickers: unknown): string[] {
if (!Array.isArray(tickers)) {
throw new Error('Invalid tickers: must be an array');
}
if (tickers.length === 0) {
throw new Error('Invalid tickers: array cannot be empty');
}
return tickers.map((t) => {
if (typeof t !== 'string') {
throw new Error(`Invalid ticker in array: ${t} (expected string)`);
}
return sanitizeTicker(t);
});
}
/**
* Sanitize a string field.
* - Trims whitespace
* - Validates non-empty
* - Optional: enforces max length
*
* @param value The string value
* @param fieldName Name of the field (for error messages)
* @param maxLength Maximum allowed length (optional)
* @returns Trimmed string
* @throws Error if value is invalid
*/
export function sanitizeString(value: unknown, fieldName: string, maxLength?: number): string {
if (typeof value !== 'string') {
throw new Error(`Invalid ${fieldName}: must be a string`);
}
const trimmed = value.trim();
if (!trimmed) {
throw new Error(`Invalid ${fieldName}: cannot be empty or whitespace`);
}
if (maxLength && trimmed.length > maxLength) {
throw new Error(`Invalid ${fieldName}: exceeds max length of ${maxLength} characters`);
}
return trimmed;
}
/**
* Sanitize a number field.
* - Validates it's a number
* - Optional: enforces min/max bounds
*
* @param value The numeric value
* @param fieldName Name of the field (for error messages)
* @param min Minimum allowed value (optional)
* @param max Maximum allowed value (optional)
* @returns The validated number
* @throws Error if value is invalid
*/
export function sanitizeNumber(
value: unknown,
fieldName: string,
options?: { min?: number; max?: number },
): number {
const num = typeof value === 'number' ? value : Number(value);
if (isNaN(num)) {
throw new Error(`Invalid ${fieldName}: must be a valid number`);
}
if (options?.min !== undefined && num < options.min) {
throw new Error(`Invalid ${fieldName}: must be at least ${options.min}`);
}
if (options?.max !== undefined && num > options.max) {
throw new Error(`Invalid ${fieldName}: must be at most ${options.max}`);
}
return num;
}
/**
* Sanitize an ISO date string.
* - Validates it's a valid ISO date
* - Converts to string format YYYY-MM-DD
*
* @param value The date value (ISO string or Date)
* @param fieldName Name of the field (for error messages)
* @returns Date as YYYY-MM-DD string
* @throws Error if date is invalid
*/
export function sanitizeDate(value: unknown, fieldName: string): string {
let date: Date | null = null;
if (typeof value === 'string') {
date = new Date(value);
} else if (value instanceof Date) {
date = value;
}
if (!date || isNaN(date.getTime())) {
throw new Error(`Invalid ${fieldName}: must be a valid date`);
}
return date.toISOString().slice(0, 10); // YYYY-MM-DD
}