Skip to content

適切な箇所に@Throwsをつける #84 #85

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.*
Expand All @@ -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 {
Expand All @@ -34,23 +36,47 @@ 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,
perPage: Int = SEARCH_PER_PAGE,
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") }
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,5 @@ internal object NetworkModule {

@Singleton
@Provides
fun provideKtorHandler(
@IoDispatcher dispatcher: CoroutineDispatcher,
format: Json
): KtorHandler = KtorHandler(dispatcher, format)
fun provideKtorHandler(): KtorHandler = KtorHandler()
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ 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

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? {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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
}
}