145 lines
3.4 KiB
TypeScript
Raw Normal View History

// Modern validation utilities with ES2024 features
/**
* Modern validation result type
*/
2025-08-21 15:24:40 +09:00
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
*/
2025-08-22 17:02:49 +09:00
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> {
2025-08-21 15:24:40 +09:00
if (!email || typeof email !== "string") {
return failure("Email is required");
}
2025-08-21 15:24:40 +09:00
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2025-08-21 15:24:40 +09:00
if (!emailRegex.test(email)) {
2025-08-21 15:24:40 +09:00
return failure("Invalid email format");
}
2025-08-21 15:24:40 +09:00
const trimmed = email.trim().toLowerCase();
2025-08-21 15:24:40 +09:00
if (trimmed.length > 254) {
2025-08-21 15:24:40 +09:00
return failure("Email is too long");
}
2025-08-21 15:24:40 +09:00
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> {
2025-08-21 15:24:40 +09:00
if (!phone || typeof phone !== "string") {
return failure("Phone number is required");
}
2025-08-21 15:24:40 +09:00
const phoneRegex = /^\+?[\d\s\-()]{10,}$/;
if (!phoneRegex.test(phone)) {
2025-08-21 15:24:40 +09:00
return failure("Invalid phone number format");
}
2025-08-21 15:24:40 +09:00
const cleaned = phone.replace(/[\s\-()]/g, "");
if (cleaned.length < 10) {
2025-08-21 15:24:40 +09:00
return failure("Phone number is too short");
}
2025-08-21 15:24:40 +09:00
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 {
2025-08-22 17:02:49 +09:00
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
2025-08-21 15:24:40 +09:00
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, "")
.replace(/<[^>]*>/g, "")
.trim();
}
/**
* Formats currency amount
*/
2025-08-21 15:24:40 +09:00
export function formatCurrency(amount: number, currency = "USD"): string {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency,
}).format(amount);
}
/**
* Formats date to locale string
*/
2025-08-21 15:24:40 +09:00
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;
2025-08-21 15:24:40 +09:00
return str.slice(0, maxLength - 3) + "...";
}