All files / src/validation validators.ts

97.14% Statements 34/35
98% Branches 49/50
100% Functions 9/9
97.05% Lines 33/34

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    1x     186x       15x       42x 19x     23x 5x     18x       42x 12x     30x   30x 1x             29x 1x             28x       42x 37x     5x   5x       5x 1x     4x 1x     3x       42x 40x     2x           42x 36x     6x           42x               210x    
import type { FieldValue, NormalizedField, ValidationMessageKey } from "../schema/types";
 
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
 
function isEmpty(value: FieldValue): boolean {
  return value === undefined || value === null || value === "";
}
 
function message(field: NormalizedField, key: ValidationMessageKey, fallback: string): string {
  return field.validationMessages?.[key] ?? fallback;
}
 
export function validateRequired(field: NormalizedField, value: FieldValue): string | null {
  if (!field.required) {
    return null;
  }
 
  if (field.type === "checkbox") {
    return value === true ? null : message(field, "required", `${field.label} is required.`);
  }
 
  return isEmpty(value) ? message(field, "required", `${field.label} is required.`) : null;
}
 
export function validateStringLength(field: NormalizedField, value: FieldValue): string | null {
  if (isEmpty(value)) {
    return null;
  }
 
  const valueAsString = String(value);
 
  if (field.minLength !== undefined && valueAsString.length < field.minLength) {
    return message(
      field,
      "minLength",
      `${field.label} must be at least ${field.minLength} characters.`
    );
  }
 
  if (field.maxLength !== undefined && valueAsString.length > field.maxLength) {
    return message(
      field,
      "maxLength",
      `${field.label} must be no more than ${field.maxLength} characters.`
    );
  }
 
  return null;
}
 
export function validateNumberRange(field: NormalizedField, value: FieldValue): string | null {
  if (isEmpty(value) || (field.min === undefined && field.max === undefined)) {
    return null;
  }
 
  const numberValue = Number(value);
 
  Iif (Number.isNaN(numberValue)) {
    return message(field, "number", `${field.label} must be a number.`);
  }
 
  if (field.min !== undefined && numberValue < field.min) {
    return message(field, "min", `${field.label} must be at least ${field.min}.`);
  }
 
  if (field.max !== undefined && numberValue > field.max) {
    return message(field, "max", `${field.label} must be no more than ${field.max}.`);
  }
 
  return null;
}
 
export function validatePattern(field: NormalizedField, value: FieldValue): string | null {
  if (isEmpty(value) || !field.pattern) {
    return null;
  }
 
  return new RegExp(field.pattern).test(String(value))
    ? null
    : message(field, "pattern", `${field.label} has an invalid format.`);
}
 
export function validateEmail(field: NormalizedField, value: FieldValue): string | null {
  if (isEmpty(value) || field.type !== "email") {
    return null;
  }
 
  return emailPattern.test(String(value))
    ? null
    : message(field, "email", `${field.label} must be a valid email address.`);
}
 
export function runBuiltInValidators(field: NormalizedField, value: FieldValue): string[] {
  const results = [
    validateRequired(field, value),
    validateStringLength(field, value),
    validateNumberRange(field, value),
    validatePattern(field, value),
    validateEmail(field, value)
  ];
 
  return results.filter((result): result is string => Boolean(result));
}