Валидация на стороне клиента и сервера

Библиотека Cleave.js не является инструментом валидации в строгом смысле. Её основная задача — форматирование пользовательского ввода в реальном времени: добавление разделителей, группировка цифр, маскировка структуры значений (телефоны, даты, кредитные карты, числовые значения).

Ключевой принцип при работе с ней в системах валидации:

форматирование и валидация должны быть разделены

  • Cleave.js формирует отображаемое значение
  • валидация всегда работает с «сырыми» данными

Разделение отображаемого и фактического значения

Cleave.js хранит два состояния:

  • formatted value — значение в поле ввода (с пробелами, дефисами и т. п.)
  • raw value — «чистое» значение без форматирования

Для валидации критически важно использовать именно raw value.

Пример получения:

const cleave = new Cleave(input, {
  phone: true,
  phoneRegionCode: 'KZ'
});

// получение "сырого" значения
const raw = cleave.getRawValue();

Ошибка проектирования

Использование formatted value в валидации приводит к проблемам:

  • ложные ошибки из-за пробелов и разделителей
  • некорректная серверная интерпретация
  • различия между клиентом и сервером

Клиентская валидация с Cleave.js

Базовый подход

Клиентская валидация должна строиться в два этапа:

  1. Cleave.js отвечает за структуру ввода
  2. отдельный слой проверяет корректность значения

Использование событий изменения значения

Cleave.js предоставляет событие изменения:

const cleave = new Cleave(input, {
  numeral: true,
  onValueChanged: function (e) {
    const rawValue = e.target.rawValue;
    validate(rawValue);
  }
});

В этом контексте:

  • e.target.value — форматированное значение
  • e.target.rawValue — значение для логики

HTML5-валидация и Cleave.js

HTML5 constraints работают только с итоговым значением поля, поэтому требуется синхронизация:

input.addEventListener('input', () => {
  input.setCustomValidity('');
});

Однако при использовании Cleave.js важно помнить:

  • браузер видит только formatted value
  • ограничения pattern могут конфликтовать с форматированием

Пример интеграции с кастомной валидацией

function validatePhone(raw) {
  return /^\d{10,15}$/.test(raw);
}

const cleave = new Cleave(input, {
  phone: true,
  onValueChanged: function (e) {
    const raw = e.target.rawValue;

    if (!validatePhone(raw)) {
      input.setCustomValidity('Неверный номер телефона');
    } else {
      input.setCustomValidity('');
    }
  }
});

Серверная валидация как основной уровень защиты

Клиентская валидация в любой системе считается вспомогательной. Основной контроль всегда выполняется на сервере.

Принцип

Сервер никогда не должен доверять форматированным данным, даже если они прошли Cleave.js.


Нормализация входных данных

Перед валидацией данные приводятся к каноническому виду:

  • удаление пробелов
  • удаление разделителей
  • приведение к стандартному формату

Пример:

function normalizePhone(value) {
  return value.replace(/\D/g, '');
}

Серверная валидация телефона

function isValidPhone(raw) {
  const normalized = normalizePhone(raw);
  return normalized.length >= 10 && normalized.length <= 15;
}

Кредитные карты и Luhn-алгоритм

При работе с Cleave.js часто используется маска для карточек:

const cleave = new Cleave(input, {
  creditCard: true
});

На сервере обязательно выполняется проверка:

  • длина номера
  • допустимые BIN-диапазоны
  • алгоритм Луна
function luhnCheck(number) {
  let sum = 0;
  let double = false;

  for (let i = number.length - 1; i >= 0; i--) {
    let digit = parseInt(number[i], 10);

    if (double) {
      digit *= 2;
      if (digit > 9) digit -= 9;
    }

    sum += digit;
    double = !double;
  }

  return sum % 10 === 0;
}

Синхронизация клиентской и серверной логики

Ключевая проблема интеграции Cleave.js — различие между форматами данных.

Типичный поток данных

  1. Пользователь вводит значение
  2. Cleave.js форматирует его
  3. клиент отправляет raw value или formatted value
  4. сервер нормализует
  5. сервер валидирует

Рекомендуемый контракт API

Передавать следует:

  • только raw value
  • либо отдельное поле для raw значения

Пример:

{
  "phone_raw": "77001234567",
  "phone_formatted": "+7 (700) 123-45-67"
}

Оптимально:

  • использовать только phone_raw
  • formatted хранить исключительно для UI

Типовые сценарии валидации

Телефонные номера

Особенности:

  • разные страны
  • разные длины
  • наличие кода региона

Cleave.js обеспечивает визуальную структуру, но не проверяет:

  • существование номера
  • корректность кода страны

Поэтому серверная логика обязательна.


Даты

Cleave.js может форматировать даты:

new Cleave(input, {
  date: true,
  datePattern: ['Y', 'm', 'd']
});

Но валидация должна учитывать:

  • календарную корректность
  • високосные годы
  • диапазоны дат

Пример серверной проверки:

function isValidDate(y, m, d) {
  const date = new Date(y, m - 1, d);
  return date.getFullYear() === y &&
         date.getMonth() === m - 1 &&
         date.getDate() === d;
}

Числовые значения

Cleave.js поддерживает форматирование чисел:

  • разделители тысяч
  • десятичные знаки

Но сервер должен:

  • удалять разделители
  • проверять диапазоны
  • учитывать локаль
function parseNumber(value) {
  return Number(value.replace(/,/g, ''));
}

Антипаттерны при использовании Cleave.js

Использование formatted value как основного источника

Проблема:

  • ломает серверную логику
  • приводит к ошибкам парсинга

Попытка заменить валидацию масками

Cleave.js не предназначен для:

  • проверки существования данных
  • бизнес-валидации
  • проверки целостности

Дублирование логики без нормализации

Если клиент и сервер используют разные правила очистки:

  • появляются расхождения
  • тесты становятся нестабильными

Архитектура валидационного слоя

Корректная схема интеграции выглядит так:

Клиент

  • Cleave.js → форматирование
  • lightweight validation → UX-подсказки
  • отправка raw value

Сервер

  • нормализация
  • строгая валидация
  • бизнес-правила

Практика построения единого формата

Общая функция нормализации

function normalize(input, type) {
  switch (type) {
    case 'phone':
      return input.replace(/\D/g, '');

    case 'number':
      return input.replace(/,/g, '');

    case 'date':
      return input;

    default:
      return input.trim();
  }
}

Унификация контрактов

Важно, чтобы все поля имели:

  • определённый raw формат
  • документированное правило очистки
  • одинаковую логику на всех слоях системы

Поведение при ошибках ввода

При использовании Cleave.js возможны ситуации:

  • пользователь вводит неполные данные
  • поле визуально корректно, но логически нет
  • маска скрывает отсутствие данных

Поэтому валидация должна учитывать:

  • минимальную длину raw value
  • обязательные сегменты
  • состояние «незавершённого ввода»

Итоговая модель взаимодействия

  • Cleave.js отвечает только за структуру ввода
  • клиентская логика — за UX-проверки
  • сервер — за финальную и единственно достоверную валидацию
  • raw value — единственный источник истины