Как адаптировать тип ошибки в Result к доменному типу, сохранив значение успешного результата?
Используйте mapError. Он преобразует только значение ошибки, оставляя успешный результат и его тип без изменений.
Это удобно при переходе между слоями: инфраструктурная ошибка превращается в доменную, но успешное значение передаётся дальше как есть.
Result представляет успех или ошибку как обычное значение. Такой подход позволяет явно передавать тип ошибки между слоями и преобразовывать его без немедленного перехвата.
Проблема возникает, когда разные слои используют разные модели ошибок. Инфраструктурный слой может знать о тайм-ауте сети, а доменному слою нужен более общий случай недоступности сервиса.
Пусть операция возвращает Result<Int, TransportError>, но публичный API должен возвращать Result<Int, DomainError>. Нельзя просто присвоить один результат другому: тип ошибки является частью типа Result.
При ручном разборе результата легко случайно изменить успешное значение, забыть обработать ветку или дублировать код. Требуется преобразовать только ошибочную ветвь и сохранить семантику успеха.
Метод mapError получает исходную ошибку и возвращает новую ошибку. Если результат содержит success, замыкание не выполняется, а успешное значение переносится без изменений.
После преобразования domain имеет тип Result<Int, DomainError>. Значение success осталось бы тем же Int, а failure(.timeout) превратился бы в failure(.serviceUnavailable).
mapError не обрабатывает ошибку в смысле восстановления, не выбрасывает её и не превращает результат в успех. Если требуется изменить успешное значение, применяется map; если нужно продолжить цепочку операцией, которая сама возвращает Result, используется flatMap.
Преобразование должно быть осмысленным: не следует без причины сводить все ошибки к одному случаю, если вызывающему коду важны разные причины. Иначе теряется диагностическая информация и ухудшается возможность корректного поведения клиента.
Сетевой репозиторий возвращает ошибки транспорта: тайм-аут, отсутствие соединения и недопустимый ответ. Экрану приложения не нужны детали HTTP или сетевого стека; ему достаточно различать недоступность сервиса и некорректные данные.
Первый вариант — передавать инфраструктурный Result наверх. Это сохраняет детали, но связывает UI с нижним слоем и затрудняет замену транспорта. Второй вариант — вручную разбирать Result в каждом месте вызова; такой код дублируется и может по-разному классифицировать одну и ту же ошибку.
Выбранное решение — выполнить mapError на границе слоя и преобразовать ошибки в доменный тип. Успешное значение не меняется, а классификация ошибок сосредоточена в одном месте. В результате верхний слой получает стабильный контракт и не зависит от деталей транспорта.
mapError, если Result содержит успех?Нет. Замыкание вызывается только для значения в ветке failure. Успешное значение возвращается без преобразования, поэтому mapError безопасен для цепочек, где нужно адаптировать только ошибки.
mapError превратить ошибку в успешный результат?Нет. Он всегда возвращает Result с той же успешной веткой и преобразованной ошибочной веткой. Для восстановления после ошибки нужно явно разобрать результат и вернуть success, либо построить отдельную операцию преобразования с подходящей семантикой.
mapError замыкание, которое само выбрасывает ошибку?Нет, обычный mapError принимает невыбрасывающее замыкание. Его задача — синхронно преобразовать уже имеющееся значение ошибки, а не запускать новый бросающий процесс. Если преобразование само может завершиться ошибкой, нужно отдельно спроектировать такую операцию и явно определить, как представить обе возможные причины сбоя.