diff --git a/data/src/main/java/com/leoleo/androidgithubsearch/data/api/GithubApi.kt b/data/src/main/java/com/leoleo/androidgithubsearch/data/api/GithubApi.kt index ce53068..dc567de 100644 --- a/data/src/main/java/com/leoleo/androidgithubsearch/data/api/GithubApi.kt +++ b/data/src/main/java/com/leoleo/androidgithubsearch/data/api/GithubApi.kt @@ -1,8 +1,10 @@ package com.leoleo.androidgithubsearch.data.api import com.leoleo.androidgithubsearch.data.BuildConfig +import com.leoleo.androidgithubsearch.data.api.response.GithubErrorResponse import com.leoleo.androidgithubsearch.data.api.response.RepositoryDetailResponse import com.leoleo.androidgithubsearch.data.api.response.SearchRepositoryResponse +import com.leoleo.androidgithubsearch.domain.exception.ApiErrorType import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.engine.android.* @@ -14,7 +16,7 @@ import io.ktor.http.* import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json -internal class GithubApi(private val format: Json) { +internal class GithubApi(private val format: Json, private val ktorHandler: KtorHandler) { private val httpClient: HttpClient by lazy { HttpClient(Android) { defaultRequest { @@ -34,10 +36,34 @@ internal class GithubApi(private val format: Json) { logger = AppHttpLogger() level = LogLevel.BODY } - expectSuccess = true + expectSuccess = true // HttpResponseValidatorで必要な設定. + HttpResponseValidator { + handleResponseExceptionWithRequest { e, _ -> + when (e) { + is ClientRequestException -> { // ktor: 400番台のエラー + val errorResponse = e.response + val message = + format.decodeFromString<GithubErrorResponse>(errorResponse.body()).message + when (errorResponse.status) { + HttpStatusCode.Unauthorized -> throw ApiErrorType.UnAuthorized( + message + ) + HttpStatusCode.NotFound -> throw ApiErrorType.NotFound(message) + HttpStatusCode.Forbidden -> throw ApiErrorType.Forbidden(message) + HttpStatusCode.UnprocessableEntity -> { + throw ApiErrorType.UnprocessableEntity(message) + } + else -> throw ApiErrorType.Unknown(message) + } + } + else -> ktorHandler.handleResponseException(e) + } + } + } } } + @kotlin.jvm.Throws(ApiErrorType::class) suspend fun searchRepositories( query: String, page: Int, @@ -45,12 +71,12 @@ internal class GithubApi(private val format: Json) { sort: String = "stars" ): SearchRepositoryResponse { /* - サーバーサイドのAPI開発が完了するまではFlavorをstubにし、開発を進める. - return format.decodeFromStubData<SearchRepositoryResponse>( - context, - format, - "search_repositories_success.json" - ) + // サーバーサイドのAPI開発が完了するまではFlavorをstubにし、開発を進める. + return format.decodeFromStubData<SearchRepositoryResponse>( + context, + format, + "search_repositories_success.json" + ) */ val response: HttpResponse = httpClient.get { url { path("search", "repositories") } @@ -62,6 +88,7 @@ internal class GithubApi(private val format: Json) { return format.decodeFromString(response.body()) } + @kotlin.jvm.Throws(ApiErrorType::class) suspend fun fetchRepositoryDetail( ownerName: String, repositoryName: String diff --git a/data/src/main/java/com/leoleo/androidgithubsearch/data/api/KtorHandler.kt b/data/src/main/java/com/leoleo/androidgithubsearch/data/api/KtorHandler.kt index 5ae4c69..bfa1730 100644 --- a/data/src/main/java/com/leoleo/androidgithubsearch/data/api/KtorHandler.kt +++ b/data/src/main/java/com/leoleo/androidgithubsearch/data/api/KtorHandler.kt @@ -1,55 +1,29 @@ package com.leoleo.androidgithubsearch.data.api -import com.leoleo.androidgithubsearch.data.api.response.GithubErrorResponse import com.leoleo.androidgithubsearch.domain.exception.ApiErrorType import io.ktor.client.call.* import io.ktor.client.network.sockets.* import io.ktor.client.plugins.* import io.ktor.http.* -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.withContext -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json import java.net.UnknownHostException -internal class KtorHandler( - private val dispatcher: CoroutineDispatcher, - private val format: Json, -) { - suspend fun <T> dataOrThrow(apiCall: suspend () -> T): T { - return withContext(dispatcher) { - try { - apiCall.invoke() - } catch (e: Throwable) { - when (e) { - is UnknownHostException, is HttpRequestTimeoutException, is ConnectTimeoutException, is SocketTimeoutException -> { - throw ApiErrorType.Network - } - // ktor: 300番台のエラー - is RedirectResponseException -> throw ApiErrorType.Redirect - is ClientRequestException -> { // ktor: 400番台のエラー - val errorResponse = e.response - val message = - format.decodeFromString<GithubErrorResponse>(errorResponse.body()).message - when (errorResponse.status) { - HttpStatusCode.Unauthorized -> throw ApiErrorType.UnAuthorized( - message - ) - HttpStatusCode.NotFound -> throw ApiErrorType.NotFound(message) - HttpStatusCode.Forbidden -> throw ApiErrorType.Forbidden(message) - HttpStatusCode.UnprocessableEntity -> { - throw ApiErrorType.UnprocessableEntity(message) - } - else -> throw ApiErrorType.Unknown(message) - } - } - // ktor: 500番台のエラー - is ServerResponseException -> throw ApiErrorType.Server - // ktor: それ以外のエラー - is ResponseException -> throw ApiErrorType.Unknown(e.localizedMessage) - else -> throw ApiErrorType.Unknown(e.localizedMessage) - } +internal class KtorHandler { + /** + * 共通のAPI エラーハンドリングはここで行う. + */ + @Throws(ApiErrorType::class) + fun handleResponseException(e: Throwable) { + when (e) { + is UnknownHostException, is HttpRequestTimeoutException, is ConnectTimeoutException, is SocketTimeoutException -> { + throw ApiErrorType.Network } + // ktor: 300番台のエラー + is RedirectResponseException -> throw ApiErrorType.Redirect + // ktor: 500番台のエラー + is ServerResponseException -> throw ApiErrorType.Server + // ktor: それ以外のエラー + is ResponseException -> throw ApiErrorType.Unknown(e.localizedMessage) + else -> throw ApiErrorType.Unknown(e.localizedMessage) } } } \ No newline at end of file diff --git a/data/src/main/java/com/leoleo/androidgithubsearch/data/di/DataSourceModule.kt b/data/src/main/java/com/leoleo/androidgithubsearch/data/di/DataSourceModule.kt index 406fba1..243170b 100644 --- a/data/src/main/java/com/leoleo/androidgithubsearch/data/di/DataSourceModule.kt +++ b/data/src/main/java/com/leoleo/androidgithubsearch/data/di/DataSourceModule.kt @@ -1,6 +1,7 @@ package com.leoleo.androidgithubsearch.data.di import com.leoleo.androidgithubsearch.data.api.GithubApi +import com.leoleo.androidgithubsearch.data.api.KtorHandler import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -22,5 +23,6 @@ import javax.inject.Singleton internal object DataSourceModule { @Singleton @Provides - fun provideGithubService(format: Json): GithubApi = GithubApi(format) + fun provideGithubService(format: Json, ktorHandler: KtorHandler): GithubApi = + GithubApi(format, ktorHandler) } \ No newline at end of file diff --git a/data/src/main/java/com/leoleo/androidgithubsearch/data/di/NetworkModule.kt b/data/src/main/java/com/leoleo/androidgithubsearch/data/di/NetworkModule.kt index ef3c56b..2e263ea 100644 --- a/data/src/main/java/com/leoleo/androidgithubsearch/data/di/NetworkModule.kt +++ b/data/src/main/java/com/leoleo/androidgithubsearch/data/di/NetworkModule.kt @@ -19,8 +19,5 @@ internal object NetworkModule { @Singleton @Provides - fun provideKtorHandler( - @IoDispatcher dispatcher: CoroutineDispatcher, - format: Json - ): KtorHandler = KtorHandler(dispatcher, format) + fun provideKtorHandler(): KtorHandler = KtorHandler() } \ No newline at end of file diff --git a/data/src/main/java/com/leoleo/androidgithubsearch/data/paging/GithubRepoPagingSource.kt b/data/src/main/java/com/leoleo/androidgithubsearch/data/paging/GithubRepoPagingSource.kt index 7637d17..ffc90da 100644 --- a/data/src/main/java/com/leoleo/androidgithubsearch/data/paging/GithubRepoPagingSource.kt +++ b/data/src/main/java/com/leoleo/androidgithubsearch/data/paging/GithubRepoPagingSource.kt @@ -4,7 +4,6 @@ import android.util.Log import androidx.paging.PagingSource import androidx.paging.PagingState import com.leoleo.androidgithubsearch.data.api.GithubApi -import com.leoleo.androidgithubsearch.data.api.KtorHandler import com.leoleo.androidgithubsearch.data.api.response.toModels import com.leoleo.androidgithubsearch.domain.exception.ValidationErrorType import com.leoleo.androidgithubsearch.domain.model.RepositorySummary @@ -12,7 +11,6 @@ import com.leoleo.androidgithubsearch.domain.model.RepositorySummary internal class GithubRepoPagingSource( private val query: String, private val api: GithubApi, - private val ktorHandler: KtorHandler, ) : PagingSource<Int, RepositorySummary>() { override fun getRefreshKey(state: PagingState<Int, RepositorySummary>): Int? { @@ -33,10 +31,7 @@ internal class GithubRepoPagingSource( val size = params.loadSize val from = pageNumber * size val placeholdersEnabled = params.placeholdersEnabled - val data = - ktorHandler.dataOrThrow { - api.searchRepositories(query = query, page = pageNumber).toModels() - } + val data = api.searchRepositories(query = query, page = pageNumber).toModels() // Since {INIT_PAGE_NO} is the lowest page number, return null to signify no more pages should // be loaded before it. val prevKey = if (pageNumber > INIT_PAGE_NO) pageNumber - 1 else null diff --git a/data/src/main/java/com/leoleo/androidgithubsearch/data/repository/GithubRepoRepositoryImpl.kt b/data/src/main/java/com/leoleo/androidgithubsearch/data/repository/GithubRepoRepositoryImpl.kt index 2093f5d..b79ad69 100644 --- a/data/src/main/java/com/leoleo/androidgithubsearch/data/repository/GithubRepoRepositoryImpl.kt +++ b/data/src/main/java/com/leoleo/androidgithubsearch/data/repository/GithubRepoRepositoryImpl.kt @@ -5,7 +5,6 @@ import androidx.paging.PagingConfig import androidx.paging.PagingData import com.leoleo.androidgithubsearch.data.api.GithubApi import com.leoleo.androidgithubsearch.data.api.GithubApi.Companion.SEARCH_PER_PAGE -import com.leoleo.androidgithubsearch.data.api.KtorHandler import com.leoleo.androidgithubsearch.data.api.response.toModel import com.leoleo.androidgithubsearch.data.paging.GithubRepoPagingSource import com.leoleo.androidgithubsearch.domain.model.RepositoryDetail @@ -15,15 +14,13 @@ import kotlinx.coroutines.flow.Flow import javax.inject.Inject internal class GithubRepoRepositoryImpl @Inject constructor( - private val api: GithubApi, - private val ktorHandler: KtorHandler, + private val api: GithubApi ) : GithubRepoRepository { override suspend fun getRepositoryDetail( ownerName: String, repositoryName: String - ): RepositoryDetail = - ktorHandler.dataOrThrow { api.fetchRepositoryDetail(ownerName, repositoryName).toModel() } + ): RepositoryDetail = api.fetchRepositoryDetail(ownerName, repositoryName).toModel() override fun searchRepositories(query: String): Flow<PagingData<RepositorySummary>> { return Pager( @@ -32,7 +29,7 @@ internal class GithubRepoRepositoryImpl @Inject constructor( initialLoadSize = SEARCH_PER_PAGE, enablePlaceholders = true ), - pagingSourceFactory = { GithubRepoPagingSource(query, api, ktorHandler) }, + pagingSourceFactory = { GithubRepoPagingSource(query, api) }, ).flow } } \ No newline at end of file