Обработка файлов

При работе с загрузкой данных в серверных приложениях на Node.js файлы редко валидируются напрямую через бинарное содержимое. Обычно используется промежуточное представление — объект метаданных, который формируется библиотеками вроде multer. Такой объект содержит информацию о имени файла, MIME-типе, размере и временном пути хранения.

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

Типичное представление файла:

{
  fieldname: 'avatar',
  originalname: 'photo.png',
  encoding: '7bit',
  mimetype: 'image/png',
  size: 34567,
  destination: '/uploads',
  filename: 'a1b2c3.png',
  path: '/uploads/a1b2c3.png'
}

Каждое поле может участвовать в валидации через соответствующие правила Joi.


Базовая схема валидации файлов

Основной подход заключается в описании структуры объекта файла через Joi.object():

import Joi fr om 'joi';

const fileSchema = Joi.object({
  fieldname: Joi.string().required(),
  originalname: Joi.string().required(),
  mimetype: Joi.string().required(),
  size: Joi.number().max(2 * 1024 * 1024).required(),
  path: Joi.string().required()
});

В данном случае задаются базовые ограничения:

  • size ограничивает максимальный размер файла
  • mimetype фиксирует допустимый тип содержимого
  • обязательность полей гарантирует целостность объекта

Ограничение типов файлов через MIME

Контроль формата файла часто реализуется через проверку MIME-типа. Joi позволяет использовать valid() для ограничения допустимых значений:

const imageFileSchema = Joi.object({
  mimetype: Joi.string().valid(
    'image/jpeg',
    'image/png',
    'image/webp'
  ).required()
});

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

mimetype: Joi.string().pattern(/^image\/(jpeg|png|webp)$/)

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


Проверка размера файла

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

size: Joi.number()
  .max(5 * 1024 * 1024)
  .required()

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


Валидация с использованием multer и Joi

В большинстве серверных приложений Joi используется совместно с multer, который отвечает за получение файлов.

Пример интеграции:

import multer from 'multer';

const upload = multer({ dest: 'uploads/' });

const schema = Joi.object({
  file: Joi.object({
    originalname: Joi.string().required(),
    mimetype: Joi.string().required(),
    size: Joi.number().max(1024 * 1024).required()
  }).required()
});

Обработка запроса:

app.post('/upload', upload.single('file'), (req, res) => {
  const { error } = schema.validate({ file: req.file });

  if (error) {
    return res.status(400).json(error.details);
  }

  res.status(200).send('OK');
});

Валидация массива файлов

При множественной загрузке используется Joi.array():

const multiFileSchema = Joi.object({
  files: Joi.array()
    .items(
      Joi.object({
        originalname: Joi.string().required(),
        mimetype: Joi.string().valid('image/png', 'image/jpeg').required(),
        size: Joi.number().max(2 * 1024 * 1024).required()
      })
    )
    .min(1)
    .max(10)
});

Ограничения:

  • минимальное количество файлов
  • максимальное количество файлов
  • проверка каждого элемента массива

Кастомная валидация файлов

Joi поддерживает расширение логики через custom():

const schema = Joi.object({
  file: Joi.object({
    originalname: Joi.string().required(),
    size: Joi.number().required()
  }).custom((value, helpers) => {
    if (!value.originalname.endsWith('.png')) {
      return helpers.error('file.invalidExtension');
    }

    return value;
  })
});

Кастомные правила позволяют реализовать:

  • проверку расширений
  • контроль структуры имени файла
  • бизнес-ограничения (например, запрещённые имена)

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

Хотя MIME-тип считается более надёжным источником, расширение часто используется как дополнительный фильтр:

const schema = Joi.object({
  originalname: Joi.string()
    .pattern(/\.(jpg|jpeg|png|webp)$/i)
    .required()
});

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


Санитизация имени файла

Имя файла может содержать опасные символы, пробелы или конструкции, влияющие на файловую систему.

Joi позволяет описывать базовую фильтрацию:

const schema = Joi.object({
  originalname: Joi.string()
    .pattern(/^[a-zA-Z0-9._-]+$/)
    .required()
});

Дополнительно часто используется нормализация:

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

Композиция схем для файловых сущностей

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

const userSchema = Joi.object({
  username: Joi.string().required(),
  avatar: Joi.object({
    originalname: Joi.string().required(),
    mimetype: Joi.string().valid('image/png').required(),
    size: Joi.number().max(1024 * 1024).required()
  }).required()
});

Композиция позволяет:

  • объединять файловую и текстовую валидацию
  • поддерживать единые правила для сущности
  • централизовать контроль входных данных

Обработка ошибок валидации файлов

Ошибки Joi имеют стандартизированную структуру:

{
  "message": "\"size\" must be less than or equal to 1048576",
  "path": ["file", "size"],
  "type": "number.max",
  "context": {
    "limit": 1048576,
    "value": 2097152
  }
}

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

  • path — указывает конкретное поле файла
  • type — тип нарушения правила
  • context.lim it — допустимое ограничение

Условная валидация файлов

Joi поддерживает условные правила через when():

const schema = Joi.object({
  type: Joi.string().valid('image', 'document').required(),

  file: Joi.object().when('type', {
    is: 'image',
    then: Joi.object({
      mimetype: Joi.string().valid('image/png', 'image/jpeg').required(),
      size: Joi.number().max(2 * 1024 * 1024).required()
    }),
    otherwise: Joi.object({
      mimetype: Joi.string().valid('application/pdf').required(),
      size: Joi.number().max(10 * 1024 * 1024).required()
    })
  })
});

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


Валидация потоков загрузки и промежуточных файлов

При потоковой загрузке файл может отсутствовать как готовый объект и существовать только как метаданные. В таких случаях Joi применяется к промежуточному представлению:

  • проверка пути временного файла
  • контроль существования файла
  • проверка целостности метаданных
const streamFileSchema = Joi.object({
  path: Joi.string().required(),
  size: Joi.number().positive().required(),
  mimetype: Joi.string().required()
});

Использование расширенных правил для файловых структур

При необходимости строгого контроля возможно комбинирование нескольких уровней валидации:

  • базовые правила Joi (структура объекта)
  • кастомные валидаторы (бизнес-логика)
  • внешние проверки (файловая система, антивирус, hash-суммы)

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

const schema = Joi.object({
  file: Joi.object({
    originalname: Joi.string().required(),
    size: Joi.number().required(),
    mimetype: Joi.string().required(),
    hash: Joi.string().length(64).required()
  })
});

Добавление хэша позволяет контролировать целостность файла независимо от транспортного слоя.