Email, URL, UUID

Валидация email-адресов в Zod реализуется через встроенный метод z.string().email(), который опирается на проверку соответствия строкового значения стандартному формату электронной почты. Несмотря на кажущуюся простоту, обработка email требует учета множества нюансов: допустимые символы, наличие доменной части, локальной части, ограничения длины и специфические исключения, предусмотренные RFC.

Базовая схема:

import { z } from "zod";

const EmailSchema = z.string().email();

Такой вариант применяет стандартную проверку, основанную на регулярном выражении и внутренних правилах библиотеки. Входное значение должно быть строкой, содержащей символ @, разделяющий локальную и доменную части.

Уточнение формата

Zod позволяет дополнительно задавать ограничения:

const EmailSchema = z
  .string()
  .email()
  .min(5)
  .max(254);

Ограничение длины важно, поскольку реальные email-адреса редко превышают 254 символа (ограничение стандарта SMTP).

Нормализация

Часто требуется предварительная очистка входных данных:

const EmailSchema = z
  .string()
  .trim()
  .toLowerCase()
  .email();

Приведение к нижнему регистру полезно для доменной части, так как она регистронезависима. Однако локальная часть (до @) теоретически может быть чувствительной к регистру, что иногда требует осторожности при трансформациях.

Пользовательская ошибка

const EmailSchema = z
  .string()
  .email({ message: "Некорректный email-адрес" });

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

Ограничения встроенной проверки

Стандартная проверка Zod не покрывает все возможные RFC-спецификации, например:

  • редкие валидные символы в локальной части (+, !, %)
  • международные домены (IDN)
  • специфические корпоративные форматы

Для более строгой проверки применяется refine:

const EmailSchema = z.string().refine((val) => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val);
});

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


URL

Проверка URL осуществляется через z.string().url(), который валидирует строку как корректный веб-адрес.

const UrlSchema = z.string().url();

Поддерживаются стандартные протоколы, такие как http, https, а также в зависимости от окружения — дополнительные схемы.

Структура проверки

URL разбивается на следующие компоненты:

  • протокол (scheme)
  • домен или IP-адрес
  • путь
  • параметры строки запроса
  • фрагмент (hash)

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

const result = UrlSchema.parse("https://example.com/path?query=1#section");

Ограничение протоколов

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

const UrlSchema = z.string().url().refine((val) => {
  return val.startsWith("https://");
});

Более строгий вариант:

const UrlSchema = z.string().refine((val) => {
  try {
    const url = new URL(val);
    return url.protocol === "https:";
  } catch {
    return false;
  }
});

Особенности обработки

Zod опирается на стандартные механизмы разбора URL, что означает:

  • корректная обработка IPv4 и IPv6 адресов
  • поддержка портов (:3000)
  • нормализация лишних слэшей в некоторых случаях зависит от среды выполнения

IDN-домены

Международные домены (например, с кириллицей) требуют предварительной обработки:

const UrlSchema = z.string().url().transform((val) => {
  const url = new URL(val);
  return url.toString();
});

Однако преобразование IDN в punycode часто выполняется вне Zod, на уровне вспомогательных библиотек.


UUID

UUID (Universally Unique Identifier) используется для идентификаторов объектов, особенно в распределённых системах. В Zod предусмотрена встроенная проверка через z.string().uuid().

const IdSchema = z.string().uuid();

Поддерживаемые версии

Zod позволяет ограничивать версию UUID:

const IdSchema = z.string().uuid({ version: "uuidv4" });

Наиболее распространённый вариант — UUID v4, основанный на случайных числах.

Пример:

"550e8400-e29b-41d4-a716-446655440000"

Структура UUID

UUID состоит из 32 шестнадцатеричных символов, разбитых на группы:

xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

где:

  • M — версия UUID
  • N — вариант (вариант RFC 4122)

Строгая проверка

Zod проверяет:

  • длину строки (36 символов с дефисами)
  • допустимые символы (0–9, a–f)
  • корректное расположение дефисов
  • соответствие версии (если указана)

Преобразование и нормализация

UUID иногда приводится к единому виду:

const IdSchema = z
  .string()
  .uuid()
  .transform((val) => val.toLowerCase());

Это упрощает сравнение значений в базе данных, где регистр не имеет значения.

Использование с преобразованием типов

UUID часто комбинируется с другими схемами:

const EntitySchema = z.object({
  id: z.string().uuid(),
  createdAt: z.string().datetime(),
});

Ограничения

UUID не гарантирует:

  • глобальную уникальность в абсолютном смысле без контекста генерации
  • отсутствие коллизий при некорректной реализации генератора

Поэтому валидация в Zod проверяет только синтаксическую корректность, а не семантические свойства.


Комбинированные схемы Email, URL и UUID

В реальных приложениях эти типы часто объединяются в единые структуры данных:

const UserSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  website: z.string().url().optional(),
});

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

Условные ограничения

Иногда требуется зависимая логика:

const Schema = z.object({
  email: z.string().email(),
  backupEmail: z.string().email().optional(),
}).refine((data) => {
  return data.email !== data.backupEmail;
});

Частые ошибки при работе с Email, URL и UUID

  1. Использование refine вместо встроенных методов без необходимости
  2. Попытка заменить uuid() сложными регулярными выражениями
  3. Игнорирование нормализации строк перед проверкой
  4. Смешивание валидации формата и бизнес-логики
  5. Отсутствие обработки исключений при парсинге URL через new URL()

Практика строгой типизации

Zod позволяет связывать валидацию с типами TypeScript:

type User = z.infer<typeof UserSchema>;

Это обеспечивает синхронизацию структуры данных между runtime-валидацией и компиляцией, снижая вероятность расхождений между ожидаемыми и фактическими форматами Email, URL и UUID в системе.