Валидация File объектов

В экосистеме JavaScript-валидации обработка файловых данных относится к числу наиболее чувствительных сценариев, поскольку требует одновременного контроля структуры объекта, его происхождения, метаданных и содержимого. В связке с резолверами схем валидации YupResolver используется для интеграции декларативных правил проверки с формами и API, где File-объекты передаются как часть состояния.

File в браузере представляет собой экземпляр File, унаследованный от Blob, и содержит ключевые поля: name, size, type, lastModified. Эти параметры становятся основой для валидации, поскольку именно они позволяют определить допустимость загружаемого содержимого до отправки на сервер.


Базовые принципы валидации File

При работе с файловыми полями в схемах важно учитывать несколько уровней проверки:

  • Проверка наличия файла
  • Ограничение размера
  • Фильтрация по MIME-типу
  • Проверка расширения имени файла
  • Дополнительная кастомная логика (например, проверка содержимого)

С точки зрения Yup-схемы File-объект обычно трактуется как mixed тип, что позволяет применять к нему произвольные правила трансформации и тестирования.


Объявление базовой схемы File

Типовая схема начинается с определения поля как mixed, после чего добавляются ограничения через test.

import * as Yup from "yup";

const schema = Yup.object({
  file: Yup.mixed()
    .required("Файл обязателен")
    .test("fileSize", "Файл слишком большой", (value) => {
      return value && value.size <= 5 * 1024 * 1024;
    })
    .test("fileType", "Неподдерживаемый формат", (value) => {
      return value && ["image/jpeg", "image/png"].includes(value.type);
    })
});

Здесь File-объект проверяется по двум ключевым параметрам: размер и MIME-тип. Подобный подход характерен для большинства фронтенд-форм, где требуется предварительная фильтрация до отправки данных.


Проверка обязательности и пустых значений

File-объекты имеют особенность: отсутствие значения может выражаться не только как null, но и как undefined или пустой массив в случае множественной загрузки.

Валидация обязательности должна учитывать все возможные состояния:

Yup.mixed().test(
  "required-file",
  "Файл не выбран",
  (value) => value instanceof File
);

При использовании более сложных форм часто добавляется дополнительная защита от ложноположительных значений:

Yup.mixed().test(
  "not-empty",
  "Файл отсутствует",
  (value) => {
    if (!value) return false;
    if (Array.isArray(value)) return value.length > 0;
    return value instanceof File;
  }
);

Ограничение размера файла

Ограничение размера — одна из наиболее частых задач. Оно реализуется через проверку свойства size, которое выражается в байтах.

const MAX_SIZE = 10 * 1024 * 1024;

const fileSizeSchema = Yup.mixed().test(
  "max-size",
  "Превышен допустимый размер файла",
  (file) => {
    if (!file) return false;
    return file.size <= MAX_SIZE;
  }
);

В сложных системах ограничения могут зависеть от типа файла:

const sizeRules = {
  image: 5 * 1024 * 1024,
  video: 50 * 1024 * 1024,
  document: 10 * 1024 * 1024
};

const schema = Yup.mixed().test(
  "dynamic-size",
  "Файл превышает допустимый размер",
  (file) => {
    if (!file) return false;

    const category = file.type.startsWith("image/")
      ? "image"
      : file.type.startsWith("video/")
      ? "video"
      : "document";

    return file.size <= sizeRules[category];
  }
);

Валидация MIME-типов

Проверка file.type позволяет ограничивать допустимые форматы загрузки. Это особенно важно при работе с изображениями и документами.

const allowedTypes = ["image/png", "image/jpeg", "application/pdf"];

const schema = Yup.mixed().test(
  "file-type",
  "Недопустимый тип файла",
  (file) => {
    if (!file) return false;
    return allowedTypes.includes(file.type);
  }
);

Следует учитывать, что MIME-тип может отсутствовать или быть подделан на клиентской стороне, поэтому такая проверка должна дополняться серверной валидацией.


Проверка расширения файла

Расширение имени файла используется как дополнительный уровень фильтрации:

const allowedExtensions = ["png", "jpg", "jpeg"];

const schema = Yup.mixed().test(
  "extension",
  "Недопустимое расширение файла",
  (file) => {
    if (!file || !file.name) return false;

    const ext = file.name.split(".").pop().toLowerCase();
    return allowedExtensions.includes(ext);
  }
);

Расширение не является надежным источником истины, но часто используется как UX-ограничение.


Обработка массивов файлов

При множественной загрузке поле содержит массив File-объектов. В этом случае схема усложняется, так как требуется проверять каждый элемент.

const schema = Yup.object({
  files: Yup.array()
    .of(
      Yup.mixed().test(
        "file-check",
        "Некорректный файл",
        (file) => file instanceof File
      )
    )
    .min(1, "Необходимо загрузить хотя бы один файл")
});

Дополнительно можно применять агрегационные проверки:

Yup.array().test(
  "total-size",
  "Общий размер файлов превышает лимит",
  (files) => {
    if (!files) return false;

    const total = files.reduce((acc, f) => acc + f.size, 0);
    return total <= 20 * 1024 * 1024;
  }
);

Кастомные правила валидации содержимого

File-объекты могут требовать анализа содержимого, особенно при работе с изображениями или текстовыми документами. В Yup это реализуется через асинхронные тесты.

const schema = Yup.mixed().test(
  "image-dimensions",
  "Недопустимые размеры изображения",
  async (file) => {
    if (!file) return false;

    const img = await loadImage(file);

    return img.width >= 300 && img.height >= 300;
  }
);

function loadImage(file) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onl oad = () => resolve(img);
    img.oner ror = reject;
    img.src = URL.createObjectURL(file);
  });
}

Асинхронная валидация особенно важна при проверке медиа-контента, так как требует предварительной загрузки данных в память.


Интеграция с формами через резолвер

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

import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";

const form = useForm({
  resolver: yupResolver(schema)
});

File-объекты при этом проходят через схему без дополнительной ручной обработки, что позволяет централизовать правила проверки.


Трансформация File-объектов перед валидацией

Иногда требуется нормализовать входные данные до применения тестов. Например, привести одиночный файл к массиву:

Yup.mixed().transform((value) => {
  if (value && !Array.isArray(value)) {
    return [value];
  }
  return value;
});

Подобные трансформации полезны при унификации поведения одиночных и множественных загрузок.


Комбинирование правил валидации

Реальные сценарии редко ограничиваются одной проверкой. Обычно File-объект проходит цепочку условий:

const schema = Yup.mixed()
  .required("Файл обязателен")
  .test("type", "Неверный тип", checkType)
  .test("size", "Слишком большой файл", checkSize)
  .test("name", "Некорректное имя файла", checkName)
  .test("content", "Файл не прошел проверку содержимого", checkContent);

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


Ошибки и особенности поведения File в валидации

При работе с File-объектами часто возникают нетривиальные ситуации:

  • Пустой FileList интерпретируется как undefined, а не как пустой массив
  • file.type может быть пустой строкой
  • size может быть равен 0 для пустых файлов
  • браузер может ограничивать доступ к содержимому файла до взаимодействия пользователя

Эти особенности требуют явной защиты в каждом тесте, чтобы избежать ложных ошибок валидации.


Практика построения устойчивых схем

Стабильная схема валидации файлов опирается на принцип многослойной проверки:

  • структурная проверка (File instance)
  • метаданные (name, type, size)
  • бизнес-правила (размерные лимиты, разрешенные форматы)
  • контентная проверка (при необходимости)

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