ApiExceptionMapper.kt

package com.louisfn.somovie.data.network

import androidx.annotation.AnyThread
import com.louisfn.somovie.core.common.annotation.DefaultDispatcher
import com.louisfn.somovie.core.common.extension.fromJson
import com.louisfn.somovie.core.logger.Logger
import com.louisfn.somovie.data.network.di.MoshiApiService
import com.louisfn.somovie.data.network.response.ErrorResponse
import com.louisfn.somovie.domain.exception.NoNetworkException
import com.louisfn.somovie.domain.exception.UnexpectedException
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.invoke
import java.io.IOException
import java.nio.charset.StandardCharsets
import javax.inject.Inject
import retrofit2.HttpException as RetrofitHttpException

internal class ApiExceptionMapper @Inject constructor(
    @MoshiApiService private val moshi: Moshi,
    @DefaultDispatcher private val defaultDispatcher: CoroutineDispatcher,
) {

    @AnyThread
    suspend fun map(error: Exception): Exception =
        when (error) {
            is IOException -> NoNetworkException(error)
            is RetrofitHttpException -> mapHttpException(error)
            else -> UnexpectedException(error)
        }

    @AnyThread
    private suspend fun mapHttpException(error: RetrofitHttpException): HttpException {
        val response = error.errorResponse()
        return HttpException(
            httpCode = error.code(),
            statusCode = response?.statusCode,
            statusMessage = response?.statusMessage,
        )
    }

    @AnyThread
    private suspend fun RetrofitHttpException.errorResponse(): ErrorResponse? = defaultDispatcher {
        response()?.errorBody()?.source()?.let {
            try {
                val json = String(it.readByteArray(), StandardCharsets.UTF_8)
                moshi.fromJson(json)
            } catch (e: JsonDataException) {
                Logger.e(e)
                null
            }
        }
    }
}