diff --git a/authenticator/build.gradle.kts b/authenticator/build.gradle.kts index a2bd66fe72..572f831a60 100644 --- a/authenticator/build.gradle.kts +++ b/authenticator/build.gradle.kts @@ -152,6 +152,7 @@ dependencies { implementation(files("libs/authenticatorbridge-1.0.0-release.aar")) implementation(project(":core")) + implementation(project(":network")) implementation(libs.androidx.activity.compose) implementation(libs.androidx.appcompat) diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/api/ConfigApi.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/api/ConfigApi.kt index f3e3d2aed2..2a0bcec95b 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/api/ConfigApi.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/api/ConfigApi.kt @@ -1,6 +1,7 @@ package com.bitwarden.authenticator.data.platform.datasource.network.api import com.bitwarden.authenticator.data.platform.datasource.network.model.ConfigResponseJson +import com.bitwarden.network.model.NetworkResult import retrofit2.http.GET /** @@ -9,5 +10,5 @@ import retrofit2.http.GET interface ConfigApi { @GET("config") - suspend fun getConfig(): Result + suspend fun getConfig(): NetworkResult } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/core/ResultCall.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/core/ResultCall.kt deleted file mode 100644 index 979b11ab21..0000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/core/ResultCall.kt +++ /dev/null @@ -1,98 +0,0 @@ -@file:OmitFromCoverage - -package com.bitwarden.authenticator.data.platform.datasource.network.core - -import com.bitwarden.core.annotation.OmitFromCoverage -import com.bitwarden.core.data.util.asFailure -import com.bitwarden.core.data.util.asSuccess -import okhttp3.Request -import okio.IOException -import okio.Timeout -import retrofit2.Call -import retrofit2.Callback -import retrofit2.HttpException -import retrofit2.Response -import java.lang.reflect.Type - -/** - * The integer code value for a "No Content" response. - */ -private const val NO_CONTENT_RESPONSE_CODE: Int = 204 - -/** - * A [Call] for wrapping a network request into a [Result]. - */ -@Suppress("TooManyFunctions") -class ResultCall( - private val backingCall: Call, - private val successType: Type, -) : Call> { - override fun cancel(): Unit = backingCall.cancel() - - override fun clone(): Call> = ResultCall(backingCall, successType) - - override fun enqueue(callback: Callback>): Unit = backingCall.enqueue( - object : Callback { - override fun onResponse(call: Call, response: Response) { - callback.onResponse(this@ResultCall, Response.success(response.toResult())) - } - - override fun onFailure(call: Call, t: Throwable) { - callback.onResponse(this@ResultCall, Response.success(t.toFailure())) - } - }, - ) - - @Suppress("TooGenericExceptionCaught") - override fun execute(): Response> = - try { - Response.success( - backingCall - .execute() - .toResult(), - ) - } catch (ioException: IOException) { - Response.success(ioException.toFailure()) - } catch (runtimeException: RuntimeException) { - Response.success(runtimeException.toFailure()) - } - - override fun isCanceled(): Boolean = backingCall.isCanceled - - override fun isExecuted(): Boolean = backingCall.isExecuted - - override fun request(): Request = backingCall.request() - - override fun timeout(): Timeout = backingCall.timeout() - - /** - * Synchronously send the request and return its response as a [Result]. - */ - fun executeForResult(): Result = requireNotNull(execute().body()) - - private fun Throwable.toFailure(): Result = - this - .also { - // We rebuild the URL without query params, we do not want to log those - val url = backingCall.request().url.toUrl().run { "$protocol://$authority$path" } - } - .asFailure() - - private fun Response.toResult(): Result = - if (!this.isSuccessful) { - HttpException(this).toFailure() - } else { - val body = this.body() - @Suppress("UNCHECKED_CAST") - when { - // We got a nonnull T as the body, just return it. - body != null -> body.asSuccess() - // We expected the body to be null since the successType is Unit, just return Unit. - successType == Unit::class.java -> (Unit as T).asSuccess() - // We allow null for 204's, just return null. - this.code() == NO_CONTENT_RESPONSE_CODE -> (null as T).asSuccess() - // All other null bodies result in an error. - else -> IllegalStateException("Unexpected null body!").toFailure() - } - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/core/ResultCallAdapter.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/core/ResultCallAdapter.kt deleted file mode 100644 index 801c68a857..0000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/core/ResultCallAdapter.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.bitwarden.authenticator.data.platform.datasource.network.core - -import retrofit2.Call -import retrofit2.CallAdapter -import java.lang.reflect.Type - -/** - * A [CallAdapter] for wrapping network requests into [kotlin.Result]. - */ -class ResultCallAdapter( - private val successType: Type, -) : CallAdapter>> { - - override fun responseType(): Type = successType - override fun adapt(call: Call): Call> = ResultCall(call, successType) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/core/ResultCallAdapterFactory.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/core/ResultCallAdapterFactory.kt deleted file mode 100644 index 5ef2a41a56..0000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/core/ResultCallAdapterFactory.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.bitwarden.authenticator.data.platform.datasource.network.core - -import retrofit2.Call -import retrofit2.CallAdapter -import retrofit2.Retrofit -import java.lang.reflect.ParameterizedType -import java.lang.reflect.Type - -/** - * A [CallAdapter.Factory] for wrapping network requests into [kotlin.Result]. - */ -class ResultCallAdapterFactory : CallAdapter.Factory() { - override fun get( - returnType: Type, - annotations: Array, - retrofit: Retrofit, - ): CallAdapter<*, *>? { - check(returnType is ParameterizedType) { "$returnType must be parameterized" } - val containerType = getParameterUpperBound(0, returnType) - - if (getRawType(containerType) != Result::class.java) return null - check(containerType is ParameterizedType) { "$containerType must be parameterized" } - - val requestType = getParameterUpperBound(0, containerType) - - return if (getRawType(returnType) == Call::class.java) { - ResultCallAdapter(successType = requestType) - } else { - null - } - } -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/retrofit/RetrofitsImpl.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/retrofit/RetrofitsImpl.kt index 8dff34effc..ac8bb1e08a 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/retrofit/RetrofitsImpl.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/retrofit/RetrofitsImpl.kt @@ -1,9 +1,9 @@ package com.bitwarden.authenticator.data.platform.datasource.network.retrofit -import com.bitwarden.authenticator.data.platform.datasource.network.core.ResultCallAdapterFactory import com.bitwarden.authenticator.data.platform.datasource.network.interceptor.BaseUrlInterceptor import com.bitwarden.authenticator.data.platform.datasource.network.interceptor.BaseUrlInterceptors import com.bitwarden.authenticator.data.platform.datasource.network.interceptor.HeadersInterceptor +import com.bitwarden.network.core.NetworkResultCallAdapterFactory import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient @@ -60,7 +60,7 @@ class RetrofitsImpl( private val baseRetrofitBuilder: Retrofit.Builder by lazy { Retrofit.Builder() .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) - .addCallAdapterFactory(ResultCallAdapterFactory()) + .addCallAdapterFactory(NetworkResultCallAdapterFactory()) .client(baseOkHttpClient) } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/service/ConfigServiceImpl.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/service/ConfigServiceImpl.kt index 47ec85a70e..3cfbddfa3f 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/service/ConfigServiceImpl.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/service/ConfigServiceImpl.kt @@ -2,10 +2,11 @@ package com.bitwarden.authenticator.data.platform.datasource.network.service import com.bitwarden.authenticator.data.platform.datasource.network.api.ConfigApi import com.bitwarden.authenticator.data.platform.datasource.network.model.ConfigResponseJson +import com.bitwarden.network.util.toResult /** * Default implementation of [ConfigService] for querying for app configurations. */ class ConfigServiceImpl(private val configApi: ConfigApi) : ConfigService { - override suspend fun getConfig(): Result = configApi.getConfig() + override suspend fun getConfig(): Result = configApi.getConfig().toResult() } diff --git a/authenticator/src/test/java/com/bitwarden/authenticator/data/platform/base/BaseServiceTest.kt b/authenticator/src/test/java/com/bitwarden/authenticator/data/platform/base/BaseServiceTest.kt index 33d7cee96a..7934661b70 100644 --- a/authenticator/src/test/java/com/bitwarden/authenticator/data/platform/base/BaseServiceTest.kt +++ b/authenticator/src/test/java/com/bitwarden/authenticator/data/platform/base/BaseServiceTest.kt @@ -1,7 +1,7 @@ package com.bitwarden.authenticator.data.platform.base -import com.bitwarden.authenticator.data.platform.datasource.network.core.ResultCallAdapterFactory import com.bitwarden.core.di.CoreModule +import com.bitwarden.network.core.NetworkResultCallAdapterFactory import okhttp3.HttpUrl import okhttp3.MediaType.Companion.toMediaType import okhttp3.mockwebserver.MockWebServer @@ -24,7 +24,7 @@ abstract class BaseServiceTest { protected val retrofit: Retrofit = Retrofit.Builder() .baseUrl(url.toString()) - .addCallAdapterFactory(ResultCallAdapterFactory()) + .addCallAdapterFactory(NetworkResultCallAdapterFactory()) .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) .build() diff --git a/authenticator/src/test/java/com/bitwarden/authenticator/data/platform/datasource/network/core/ResultCallAdapterTest.kt b/authenticator/src/test/java/com/bitwarden/authenticator/data/platform/datasource/network/core/ResultCallAdapterTest.kt deleted file mode 100644 index 6149e77909..0000000000 --- a/authenticator/src/test/java/com/bitwarden/authenticator/data/platform/datasource/network/core/ResultCallAdapterTest.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.bitwarden.authenticator.data.platform.datasource.network.core - -import kotlinx.coroutines.runBlocking -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.MockWebServer -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import retrofit2.Retrofit -import retrofit2.create -import retrofit2.http.GET - -class ResultCallAdapterTest { - - private val server: MockWebServer = MockWebServer().apply { start() } - private val testService: FakeService = - Retrofit - .Builder() - .baseUrl(server.url("/").toString()) - // add the adapter being tested - .addCallAdapterFactory(ResultCallAdapterFactory()) - .build() - .create() - - @AfterEach - fun after() { - server.shutdown() - } - - @Test - fun `when server returns error response code result should be failure`() = runBlocking { - server.enqueue(MockResponse().setResponseCode(500)) - val result = testService.requestWithUnitData() - Assertions.assertTrue(result.isFailure) - } - - @Test - fun `when server returns successful response result should be success`() = runBlocking { - server.enqueue(MockResponse()) - val result = testService.requestWithUnitData() - Assertions.assertTrue(result.isSuccess) - } -} - -/** - * Fake retrofit service used for testing call adapters. - */ -private interface FakeService { - @GET("/fake") - suspend fun requestWithUnitData(): Result -} diff --git a/authenticator/src/test/java/com/bitwarden/authenticator/data/platform/datasource/network/retrofit/RetrofitsTest.kt b/authenticator/src/test/java/com/bitwarden/authenticator/data/platform/datasource/network/retrofit/RetrofitsTest.kt index 1518ac1029..2191cd6a06 100644 --- a/authenticator/src/test/java/com/bitwarden/authenticator/data/platform/datasource/network/retrofit/RetrofitsTest.kt +++ b/authenticator/src/test/java/com/bitwarden/authenticator/data/platform/datasource/network/retrofit/RetrofitsTest.kt @@ -2,6 +2,7 @@ package com.bitwarden.authenticator.data.platform.datasource.network.retrofit import com.bitwarden.authenticator.data.platform.datasource.network.interceptor.BaseUrlInterceptors import com.bitwarden.authenticator.data.platform.datasource.network.interceptor.HeadersInterceptor +import com.bitwarden.network.model.NetworkResult import io.mockk.every import io.mockk.mockk import io.mockk.slot @@ -109,7 +110,7 @@ class RetrofitsTest { interface TestApi { @GET("/test") - suspend fun test(): Result + suspend fun test(): NetworkResult } /**