143 lines
3.4 KiB
TypeScript
Raw Normal View History

// Modern validation utilities with ES2024 features
/**
* Modern validation result type
*/
export type ValidationResult<T> = {
success: true;
data: T;
} | {
success: false;
error: string;
errors?: Record<string, string[]>;
};
/**
* Create a successful validation result
*/
export function success<T>(data: T): ValidationResult<T> {
return { success: true, data };
}
/**
* Create a failed validation result
*/
export function failure<T>(error: string, errors?: Record<string, string[]>): ValidationResult<T> {
return { success: false, error, errors };
}
/**
* Enhanced email validation with better error reporting
*/
export function validateEmail(email: string): ValidationResult<string> {
if (!email || typeof email !== 'string') {
return failure('Email is required');
}
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return failure('Invalid email format');
}
const trimmed = email.trim().toLowerCase();
if (trimmed.length > 254) {
return failure('Email is too long');
}
return success(trimmed);
}
/**
* Legacy boolean validation (kept for backward compatibility)
*/
export function isValidEmail(email: string): boolean {
const result = validateEmail(email);
return result.success;
}
/**
* Enhanced phone validation with better error reporting
*/
export function validatePhoneNumber(phone: string): ValidationResult<string> {
if (!phone || typeof phone !== 'string') {
return failure('Phone number is required');
}
const phoneRegex = /^\+?[\d\s\-\(\)]{10,}$/;
if (!phoneRegex.test(phone)) {
return failure('Invalid phone number format');
}
const cleaned = phone.replace(/[\s\-\(\)]/g, '');
if (cleaned.length < 10) {
return failure('Phone number is too short');
}
return success(cleaned);
}
/**
* Legacy boolean validation (kept for backward compatibility)
*/
export function isValidPhoneNumber(phone: string): boolean {
const result = validatePhoneNumber(phone);
return result.success;
}
/**
* Validates password strength
*/
export function isStrongPassword(password: string): boolean {
// At least 8 characters, one uppercase, one lowercase, one number
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/;
return passwordRegex.test(password);
}
/**
* Validates UUID format
*/
export function isValidUUID(uuid: string): boolean {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
return uuidRegex.test(uuid);
}
/**
* Sanitizes string by removing HTML tags and scripts
*/
export function sanitizeString(input: string): string {
return input
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/<[^>]*>/g, '')
.trim();
}
/**
* Formats currency amount
*/
export function formatCurrency(amount: number, currency = 'USD'): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
}).format(amount);
}
/**
* Formats date to locale string
*/
export function formatDate(date: Date | string, locale = 'en-US'): string {
const dateObj = typeof date === 'string' ? new Date(date) : date;
return dateObj.toLocaleDateString(locale);
}
/**
* Truncates string to specified length with ellipsis
*/
export function truncateString(str: string, maxLength: number): string {
if (str.length <= maxLength) return str;
return str.slice(0, maxLength - 3) + '...';
}