All files / src/lib/validation errors.ts

69.82% Statements 81/116
100% Branches 0/0
0% Functions 0/9
69.82% Lines 81/116

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 1171x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x               1x 1x 1x 1x                           1x 1x 1x 1x                 1x 1x 1x 1x         1x 1x 1x 1x       1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
/**
 * Validation Error Types and Utilities
 *
 * Standardized error format for all validation operations.
 */
 
import { ZodError, ZodIssue } from 'zod';
 
// ============================================================================
// TYPES
// ============================================================================
 
/**
 * Standardized validation error format
 */
export interface ValidationError {
  /** Field path (e.g., "email" or "address.city") */
  field: string;
  /** Human-readable error message */
  message: string;
  /** Zod error code for programmatic handling */
  code: string;
}
 
/**
 * Result of a validation operation
 */
export type ValidationResult<T> =
  | { success: true; data: T; errors?: never }
  | { success: false; data?: never; errors: ValidationError[] };
 
/**
 * Field-to-message mapping for form display
 */
export type FieldErrors = Record<string, string>;
 
// ============================================================================
// ERROR FORMATTING
// ============================================================================
 
/**
 * Convert a ZodError to an array of ValidationErrors
 */
export function formatZodError(error: ZodError): ValidationError[] {
  return error.issues.map((issue: ZodIssue) => ({
    field: issue.path.join('.') || '_root',
    message: issue.message,
    code: issue.code,
  }));
}
 
/**
 * Convert a ZodError to a field-message map for form display
 */
export function formatZodErrorToFieldMap(error: ZodError): FieldErrors {
  const fieldErrors: FieldErrors = {};

  error.issues.forEach((issue: ZodIssue) => {
    const field = issue.path.join('.') || '_root';
    // Only keep first error per field
    if (!fieldErrors[field]) {
      fieldErrors[field] = issue.message;
    }
  });

  return fieldErrors;
}
 
/**
 * Convert ValidationError array to FieldErrors map
 */
export function toFieldErrors(errors: ValidationError[]): FieldErrors {
  return errors.reduce((acc, error) => {
    if (!acc[error.field]) {
      acc[error.field] = error.message;
    }
    return acc;
  }, {} as FieldErrors);
}
 
/**
 * Get the first error message from a FieldErrors map
 */
export function getFirstError(errors: FieldErrors): string | null {
  const firstField = Object.keys(errors)[0];
  return firstField ? errors[firstField] : null;
}
 
/**
 * Check if an unknown error is a ZodError
 */
export function isZodError(error: unknown): error is ZodError {
  return error instanceof ZodError;
}
 
// ============================================================================
// ERROR MESSAGES
// ============================================================================
 
/**
 * Standard validation error messages
 */
export const ValidationMessages = {
  REQUIRED: 'This field is required',
  INVALID_EMAIL: 'Please enter a valid email address',
  INVALID_PHONE: 'Please enter a valid phone number',
  INVALID_URL: 'Please enter a valid URL',
  INVALID_DATE: 'Please enter a valid date',
  PASSWORD_TOO_SHORT: 'Password must be at least 8 characters',
  PASSWORD_REQUIREMENTS: 'Password must contain uppercase, lowercase, and number',
  MIN_LENGTH: (min: number) => `Must be at least ${min} characters`,
  MAX_LENGTH: (max: number) => `Must be less than ${max} characters`,
  MIN_VALUE: (min: number) => `Must be at least ${min}`,
  MAX_VALUE: (max: number) => `Must be at most ${max}`,
  INVALID_FORMAT: 'Invalid format',
} as const;