Валидация строк в Zod строится вокруг базового типа
z.string(), к которому последовательно добавляются
ограничения и пользовательские правила. Помимо встроенных методов
проверки длины, формата и регулярных выражений, библиотека предоставляет
механизмы для создания полностью кастомной логики через
refine, superRefine и композицию функций, что
позволяет описывать сложные доменные ограничения без выхода за пределы
схемы.
Строковый тип формируется через z.string() и может быть
расширен стандартными ограничителями:
import { z } from "zod";
const schema = z.string()
.min(5)
.max(20)
.trim();
Такие ограничения выполняются до пользовательских проверок и формируют первую линию валидации. Встроенные методы покрывают частые случаи, но не позволяют выразить специфические бизнес-правила.
Метод refine используется для добавления произвольного
условия, возвращающего булево значение. При возврате false
формируется ошибка валидации.
const usernameSchema = z.string().refine((value) => {
return /^[a-zA-Z0-9_]+$/.test(value);
}, {
message: "Недопустимые символы в строке"
});
Внутренняя функция получает значение после базовых проверок Zod. Такой подход применяется для простых условий, не требующих сложной логики или нескольких ошибок.
Ключевая особенность refine заключается в том, что он не
предоставляет контекста пути ошибки, ограничиваясь одной причиной
сбоя.
Метод superRefine применяется при необходимости
формировать несколько ошибок или анализировать строку более детально. В
отличие от refine, он предоставляет объект контекста, через
который добавляются ошибки с указанием пути и кода.
const passwordSchema = z.string().superRefine((value, ctx) => {
if (value.length < 8) {
ctx.addIssue({
code: z.ZodIssueCode.too_small,
minimum: 8,
type: "string",
inclusive: true,
message: "Слишком короткая строка"
});
}
if (!/[A-Z]/.test(value)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Отсутствует заглавная буква"
});
}
if (!/[0-9]/.test(value)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Отсутствует цифра"
});
}
});
Такая модель позволяет аккумулировать ошибки в одной проверке без преждевременного завершения валидации.
Кастомные правила часто оформляются как функции, возвращающие схемы. Это упрощает переиспользование и композицию.
const startsWith = (prefix) =>
z.string().refine((value) => value.startsWith(prefix), {
message: `Строка должна начинаться с ${prefix}`
});
const uppercaseOnly = () =>
z.string().refine((value) => value === value.toUpperCase(), {
message: "Допустимы только заглавные буквы"
});
Такой подход превращает валидацию в набор строительных блоков,
которые можно комбинировать через .and() или
последовательные вызовы.
Несмотря на наличие специализированных методов в Zod, кастомные валидаторы часто используются для уточнения правил формата.
const slugSchema = z.string().refine((value) => {
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
}, {
message: "Некорректный slug"
});
Email и URL часто требуют более строгих или ограниченных проверок, чем встроенные реализации, особенно в доменных приложениях:
const domainEmail = z.string().refine((value) => {
return value.endsWith("@example.com");
}, {
message: "Разрешены только корпоративные адреса"
});
Регулярные выражения остаются наиболее компактным способом описания формата строки, но их использование внутри Zod требует баланса между читаемостью и поддерживаемостью.
const phoneSchema = z.string().refine((value) => {
return /^\+?[0-9]{10,15}$/.test(value);
});
Однако сложные регулярные выражения часто заменяются цепочкой
проверок в superRefine, что улучшает диагностику ошибок и
читаемость кода.
В Zod возможно преобразование строки до проверки через
transform или preprocess. Это особенно важно
при нормализации данных.
const normalizedSchema = z.string()
.transform((value) => value.trim().toLowerCase())
.refine((value) => value.length > 0, {
message: "Пустая строка недопустима"
});
Предобработка позволяет отделить нормализацию от бизнес-логики, сохраняя предсказуемость схемы.
При необходимости проверки внешних данных используется асинхронный
refine. Это актуально для проверки уникальности или
обращения к API.
const uniqueUsername = z.string().refine(async (value) => {
const res = await fetch(`/api/check?username=${value}`);
const data = await res.json();
return data.available;
}, {
message: "Имя пользователя уже занято"
});
Асинхронные проверки требуют осторожности, поскольку влияют на производительность и характер выполнения схемы.
Кастомные строковые валидаторы часто комбинируются с базовыми ограничениями Zod, образуя цепочки проверок:
const schema = z.string()
.min(3)
.max(30)
.trim()
.refine((value) => /^[a-z0-9_]+$/.test(value))
.refine((value) => !value.includes("__"), {
message: "Недопустимая последовательность символов"
});
Каждое правило добавляет отдельный уровень проверки, сохраняя читаемость за счёт декларативного подхода.
Использование superRefine позволяет формировать
структурированные ошибки, привязанные к конкретным условиям строки. Это
особенно важно при сложной бизнес-валидации, где одна строка может
нарушать несколько независимых правил одновременно.
const codeSchema = z.string().superRefine((value, ctx) => {
if (!value.startsWith("ID-")) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Отсутствует префикс ID-"
});
}
if (value.length !== 10) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Неверная длина кода"
});
}
});
В практических схемах часто применяется предварительная очистка данных перед проверкой:
const cleaned = z.string()
.transform((v) => v.replace(/\s+/g, " ").trim())
.refine((v) => v.length > 0);
Подобные преобразования уменьшают количество ложных ошибок и стандартизируют входные данные.
Кастомные строковые валидаторы выполняются после базовых проверок
типа. Это означает, что refine и superRefine
не вызываются, если значение не является строкой. Такой порядок важен
при работе с неизвестными входными данными.
Дополнительно стоит учитывать, что каждая цепочка refine
добавляет отдельный этап выполнения, что может влиять на
производительность при большом количестве правил или при использовании
асинхронных проверок.
Комбинация встроенных ограничений и кастомной логики формирует гибкую систему валидации, позволяющую описывать как простые форматы строк, так и сложные доменные правила без выхода за пределы декларативной модели схем.