Trim и нормализация

Валидация строк в схемах Joi часто требует предварительной очистки пользовательского ввода. Одной из базовых операций является удаление пробелов в начале и конце строки.

В Joi это реализуется через метод trim():

const schema = Joi.string().trim();

При использовании данного метода строка автоматически нормализуется:

  • удаляются пробелы в начале строки;
  • удаляются пробелы в конце строки;
  • внутренние пробелы не изменяются.

Пример поведения:

const schema = Joi.string().trim();

schema.validate("   hello world   ");
// результат: "hello world"

Важно учитывать, что trim() применяется после базовой проверки типа, но до финальной выдачи результата. Это означает, что валидация длины (min, max) будет происходить уже на очищенной строке.

const schema = Joi.string().trim().min(5);

schema.validate("   hi   ");
// после trim -> "hi"
// длина = 2 → ошибка min(5)

При необходимости можно комбинировать trim() с другими строковыми преобразованиями:

const schema = Joi.string()
  .trim()
  .lowercase()
  .min(3);

Нормализация Unicode-строк

Unicode-строки могут иметь разные формы представления одного и того же символа. Например, символ «é» может быть записан как единый код или как комбинация e + акцент.

Для унификации используется метод normalize().

В Joi он применяется следующим образом:

const schema = Joi.string().normalize();

По умолчанию используется форма NFC (Normalization Form Canonical Composition).


Формы нормализации

Метод normalize() поддерживает стандартные формы Unicode:

  • NFC — каноническая композиция (по умолчанию)
  • NFD — каноническая декомпозиция
  • NFKC — совместимая композиция
  • NFKD — совместимая декомпозиция

Пример явного указания формы:

const schema = Joi.string().normalize('NFKC');

Практическое значение нормализации

Нормализация используется для устранения скрытых различий в строках, которые визуально идентичны, но отличаются внутренним представлением.

Типичные проблемы без нормализации:

  • сравнение строк может давать ложный результат;
  • поиск в базе данных становится нестабильным;
  • возможны дубликаты записей.

Пример:

const schema = Joi.string().normalize('NFC');

schema.validate("e\u0301"); // "é"
schema.validate("\u00e9");  // "é"

После нормализации оба варианта приводятся к единому виду.


Совместное использование trim и normalize

В реальных схемах очистка строки часто требует комбинации операций:

const schema = Joi.string()
  .trim()
  .normalize('NFC');

Порядок операций имеет значение:

  1. trim() удаляет внешние пробелы;
  2. normalize() приводит строку к единой Unicode-форме.

Такой подход обеспечивает предсказуемое поведение при сравнении и хранении данных.


Влияние на валидацию и преобразование данных

Оба метода являются частью этапа преобразования значения. Это означает, что итоговое значение, возвращаемое после validate, уже будет изменено.

const schema = Joi.string().trim().normalize();

const { value } = schema.validate("   café\u0301   ");
// value: "café́" (в нормализованной форме без внешних пробелов)

Это особенно важно при работе с:

  • пользовательскими именами;
  • email-адресами (хотя email требует отдельной схемы);
  • поисковыми запросами;
  • идентификаторами в системах хранения данных.

Ограничения и особенности поведения

Методы trim() и normalize() не изменяют семантику строки, а только её представление.

Не обрабатываются:

  • внутренние пробелы (для этого используются регулярные правила);
  • регистр (требуется lowercase() или uppercase());
  • специальные символы, не относящиеся к Unicode-нормализации.

Также следует учитывать, что normalize() может увеличивать или уменьшать длину строки, что влияет на правила min() и max().

const schema = Joi.string()
  .normalize('NFKD')
  .max(10);

В таком случае проверка длины выполняется уже после преобразования.


Комбинация с другими строковыми преобразованиями Joi

Методы очистки часто используются вместе с другими встроенными трансформациями:

const schema = Joi.string()
  .trim()
  .normalize()
  .lowercase()
  .replace(/\s+/g, ' ');

Такая цепочка обеспечивает:

  • удаление внешних пробелов;
  • унификацию Unicode;
  • приведение к нижнему регистру;
  • нормализацию внутренних пробелов.

Поведение при пустых строках

После применения trim() строка может стать пустой:

Joi.string().trim().min(1);

Строка " " после обработки превращается в "", что приводит к ошибке валидации при min(1).


Влияние на сравнение и уникальность данных

При использовании Joi в слоях валидации API важно учитывать, что trim() и normalize() изменяют фактическое значение.

Это влияет на:

  • проверку уникальности записей в базе;
  • генерацию ключей;
  • кэширование;
  • сравнение строковых идентификаторов.

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


Поведение в связке с кастомными валидаторами

При использовании custom() важно учитывать, что входное значение уже изменено:

const schema = Joi.string()
  .trim()
  .normalize()
  .custom((value) => {
    return value.startsWith('A') ? value : null;
  });

Кастомная функция получает уже очищенную и нормализованную строку, а не исходный ввод.


Итоговое значение в pipeline Joi

Внутренний порядок обработки строки в Joi можно представить так:

  1. проверка типа (string);
  2. преобразования (trim, normalize, lowercase и др.);
  3. кастомные трансформации (custom);
  4. проверки ограничений (min, max, pattern);
  5. возврат результата.

Такой порядок делает поведение предсказуемым и позволяет строить строгие схемы валидации с контролируемой очисткой входных данных.