PM-14805: Use network result in all Retrofit API requests (#4286)

This commit is contained in:
David Perez 2024-11-11 16:17:57 -06:00 committed by GitHub
parent a8416b073e
commit f33296b44f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 524 additions and 323 deletions

View file

@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.DeleteAccountReque
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyOtpRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyOtpRequestJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.HTTP import retrofit2.http.HTTP
import retrofit2.http.POST import retrofit2.http.POST
@ -18,43 +19,43 @@ interface AuthenticatedAccountsApi {
* Converts the currently active account to a key-connector account. * Converts the currently active account to a key-connector account.
*/ */
@POST("/accounts/convert-to-key-connector") @POST("/accounts/convert-to-key-connector")
suspend fun convertToKeyConnector(): Result<Unit> suspend fun convertToKeyConnector(): NetworkResult<Unit>
/** /**
* Creates the keys for the current account. * Creates the keys for the current account.
*/ */
@POST("/accounts/keys") @POST("/accounts/keys")
suspend fun createAccountKeys(@Body body: CreateAccountKeysRequest): Result<Unit> suspend fun createAccountKeys(@Body body: CreateAccountKeysRequest): NetworkResult<Unit>
/** /**
* Deletes the current account. * Deletes the current account.
*/ */
@HTTP(method = "DELETE", path = "/accounts", hasBody = true) @HTTP(method = "DELETE", path = "/accounts", hasBody = true)
suspend fun deleteAccount(@Body body: DeleteAccountRequestJson): Result<Unit> suspend fun deleteAccount(@Body body: DeleteAccountRequestJson): NetworkResult<Unit>
@POST("/accounts/request-otp") @POST("/accounts/request-otp")
suspend fun requestOtp(): Result<Unit> suspend fun requestOtp(): NetworkResult<Unit>
@POST("/accounts/verify-otp") @POST("/accounts/verify-otp")
suspend fun verifyOtp( suspend fun verifyOtp(
@Body body: VerifyOtpRequestJson, @Body body: VerifyOtpRequestJson,
): Result<Unit> ): NetworkResult<Unit>
/** /**
* Resets the temporary password. * Resets the temporary password.
*/ */
@HTTP(method = "PUT", path = "/accounts/update-temp-password", hasBody = true) @HTTP(method = "PUT", path = "/accounts/update-temp-password", hasBody = true)
suspend fun resetTempPassword(@Body body: ResetPasswordRequestJson): Result<Unit> suspend fun resetTempPassword(@Body body: ResetPasswordRequestJson): NetworkResult<Unit>
/** /**
* Resets the password. * Resets the password.
*/ */
@HTTP(method = "POST", path = "/accounts/password", hasBody = true) @HTTP(method = "POST", path = "/accounts/password", hasBody = true)
suspend fun resetPassword(@Body body: ResetPasswordRequestJson): Result<Unit> suspend fun resetPassword(@Body body: ResetPasswordRequestJson): NetworkResult<Unit>
/** /**
* Sets the password. * Sets the password.
*/ */
@POST("/accounts/set-password") @POST("/accounts/set-password")
suspend fun setPassword(@Body body: SetPasswordRequestJson): Result<Unit> suspend fun setPassword(@Body body: SetPasswordRequestJson): NetworkResult<Unit>
} }

View file

@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestUpdateRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestUpdateRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
@ -22,7 +23,7 @@ interface AuthenticatedAuthRequestsApi {
suspend fun createAdminAuthRequest( suspend fun createAdminAuthRequest(
@Header("Device-Identifier") deviceIdentifier: String, @Header("Device-Identifier") deviceIdentifier: String,
@Body body: AuthRequestRequestJson, @Body body: AuthRequestRequestJson,
): Result<AuthRequestsResponseJson.AuthRequest> ): NetworkResult<AuthRequestsResponseJson.AuthRequest>
/** /**
* Updates an authentication request. * Updates an authentication request.
@ -31,13 +32,13 @@ interface AuthenticatedAuthRequestsApi {
suspend fun updateAuthRequest( suspend fun updateAuthRequest(
@Path("id") userId: String, @Path("id") userId: String,
@Body body: AuthRequestUpdateRequestJson, @Body body: AuthRequestUpdateRequestJson,
): Result<AuthRequestsResponseJson.AuthRequest> ): NetworkResult<AuthRequestsResponseJson.AuthRequest>
/** /**
* Gets a list of auth requests for this device. * Gets a list of auth requests for this device.
*/ */
@GET("/auth-requests") @GET("/auth-requests")
suspend fun getAuthRequests(): Result<AuthRequestsResponseJson> suspend fun getAuthRequests(): NetworkResult<AuthRequestsResponseJson>
/** /**
* Retrieves an existing authentication request by ID. * Retrieves an existing authentication request by ID.
@ -45,5 +46,5 @@ interface AuthenticatedAuthRequestsApi {
@GET("/auth-requests/{requestId}") @GET("/auth-requests/{requestId}")
suspend fun getAuthRequest( suspend fun getAuthRequest(
@Path("requestId") requestId: String, @Path("requestId") requestId: String,
): Result<AuthRequestsResponseJson.AuthRequest> ): NetworkResult<AuthRequestsResponseJson.AuthRequest>
} }

View file

@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
import androidx.annotation.Keep import androidx.annotation.Keep
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.PUT import retrofit2.http.PUT
import retrofit2.http.Path import retrofit2.http.Path
@ -16,5 +17,5 @@ interface AuthenticatedDevicesApi {
suspend fun updateTrustedDeviceKeys( suspend fun updateTrustedDeviceKeys(
@Path(value = "appId") appId: String, @Path(value = "appId") appId: String,
@Body request: TrustedDeviceKeysRequestJson, @Body request: TrustedDeviceKeysRequestJson,
): Result<TrustedDeviceKeysResponseJson> ): NetworkResult<TrustedDeviceKeysResponseJson>
} }

View file

@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
import androidx.annotation.Keep import androidx.annotation.Keep
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyRequestJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Url import retrofit2.http.Url
@ -15,5 +16,5 @@ interface AuthenticatedKeyConnectorApi {
suspend fun storeMasterKeyToKeyConnector( suspend fun storeMasterKeyToKeyConnector(
@Url url: String, @Url url: String,
@Body body: KeyConnectorMasterKeyRequestJson, @Body body: KeyConnectorMasterKeyRequestJson,
): Result<Unit> ): NetworkResult<Unit>
} }

View file

@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationAutoEnrollStatusResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationAutoEnrollStatusResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationResetPasswordEnrollRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationResetPasswordEnrollRequestJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.PUT import retrofit2.http.PUT
@ -20,7 +21,7 @@ interface AuthenticatedOrganizationApi {
@Path("orgId") organizationId: String, @Path("orgId") organizationId: String,
@Path("userId") userId: String, @Path("userId") userId: String,
@Body body: OrganizationResetPasswordEnrollRequestJson, @Body body: OrganizationResetPasswordEnrollRequestJson,
): Result<Unit> ): NetworkResult<Unit>
/** /**
* Checks whether this organization auto enrolls users in password reset. * Checks whether this organization auto enrolls users in password reset.
@ -28,7 +29,7 @@ interface AuthenticatedOrganizationApi {
@GET("/organizations/{identifier}/auto-enroll-status") @GET("/organizations/{identifier}/auto-enroll-status")
suspend fun getOrganizationAutoEnrollResponse( suspend fun getOrganizationAutoEnrollResponse(
@Path("identifier") organizationIdentifier: String, @Path("identifier") organizationIdentifier: String,
): Result<OrganizationAutoEnrollStatusResponseJson> ): NetworkResult<OrganizationAutoEnrollStatusResponseJson>
/** /**
* Gets the public and private keys for this organization. * Gets the public and private keys for this organization.
@ -36,5 +37,5 @@ interface AuthenticatedOrganizationApi {
@GET("/organizations/{id}/keys") @GET("/organizations/{id}/keys")
suspend fun getOrganizationKeys( suspend fun getOrganizationKeys(
@Path("id") organizationId: String, @Path("id") organizationId: String,
): Result<OrganizationKeysResponseJson> ): NetworkResult<OrganizationKeysResponseJson>
} }

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.auth.datasource.network.api package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import okhttp3.ResponseBody import okhttp3.ResponseBody
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Path import retrofit2.http.Path
@ -14,5 +15,5 @@ interface HaveIBeenPwnedApi {
suspend fun fetchBreachedPasswords( suspend fun fetchBreachedPasswords(
@Path("hashPrefix") @Path("hashPrefix")
hashPrefix: String, hashPrefix: String,
): Result<ResponseBody> ): NetworkResult<ResponseBody>
} }

View file

@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorKeyRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorKeyRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.Header import retrofit2.http.Header
@ -15,16 +16,16 @@ interface UnauthenticatedAccountsApi {
@POST("/accounts/password-hint") @POST("/accounts/password-hint")
suspend fun passwordHintRequest( suspend fun passwordHintRequest(
@Body body: PasswordHintRequestJson, @Body body: PasswordHintRequestJson,
): Result<Unit> ): NetworkResult<Unit>
@POST("/two-factor/send-email-login") @POST("/two-factor/send-email-login")
suspend fun resendVerificationCodeEmail( suspend fun resendVerificationCodeEmail(
@Body body: ResendEmailRequestJson, @Body body: ResendEmailRequestJson,
): Result<Unit> ): NetworkResult<Unit>
@POST("/accounts/set-key-connector-key") @POST("/accounts/set-key-connector-key")
suspend fun setKeyConnectorKey( suspend fun setKeyConnectorKey(
@Body body: KeyConnectorKeyRequestJson, @Body body: KeyConnectorKeyRequestJson,
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String, @Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
): Result<Unit> ): NetworkResult<Unit>
} }

View file

@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
@ -21,7 +22,7 @@ interface UnauthenticatedAuthRequestsApi {
suspend fun createAuthRequest( suspend fun createAuthRequest(
@Header("Device-Identifier") deviceIdentifier: String, @Header("Device-Identifier") deviceIdentifier: String,
@Body body: AuthRequestRequestJson, @Body body: AuthRequestRequestJson,
): Result<AuthRequestsResponseJson.AuthRequest> ): NetworkResult<AuthRequestsResponseJson.AuthRequest>
/** /**
* Queries for updates to a given auth request. * Queries for updates to a given auth request.
@ -30,5 +31,5 @@ interface UnauthenticatedAuthRequestsApi {
suspend fun getAuthRequestUpdate( suspend fun getAuthRequestUpdate(
@Path("requestId") requestId: String, @Path("requestId") requestId: String,
@Query("code") accessCode: String, @Query("code") accessCode: String,
): Result<AuthRequestsResponseJson.AuthRequest> ): NetworkResult<AuthRequestsResponseJson.AuthRequest>
} }

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.auth.datasource.network.api package com.x8bit.bitwarden.data.auth.datasource.network.api
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
@ -11,5 +12,5 @@ interface UnauthenticatedDevicesApi {
suspend fun getIsKnownDevice( suspend fun getIsKnownDevice(
@Header(value = "X-Request-Email") emailAddress: String, @Header(value = "X-Request-Email") emailAddress: String,
@Header(value = "X-Device-Identifier") deviceId: String, @Header(value = "X-Device-Identifier") deviceId: String,
): Result<Boolean> ): NetworkResult<Boolean>
} }

View file

@ -10,6 +10,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJso
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.SendVerificationEmailRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Body import retrofit2.http.Body
@ -46,12 +47,12 @@ interface UnauthenticatedIdentityApi {
@Field(value = "twoFactorProvider") twoFactorMethod: String?, @Field(value = "twoFactorProvider") twoFactorMethod: String?,
@Field(value = "twoFactorRemember") twoFactorRemember: String?, @Field(value = "twoFactorRemember") twoFactorRemember: String?,
@Field(value = "authRequest") authRequestId: String?, @Field(value = "authRequest") authRequestId: String?,
): Result<GetTokenResponseJson.Success> ): NetworkResult<GetTokenResponseJson.Success>
@GET("/sso/prevalidate") @GET("/sso/prevalidate")
suspend fun prevalidateSso( suspend fun prevalidateSso(
@Query("domainHint") organizationIdentifier: String, @Query("domainHint") organizationIdentifier: String,
): Result<PrevalidateSsoResponseJson> ): NetworkResult<PrevalidateSsoResponseJson>
/** /**
* This call needs to be synchronous so we need it to return a [Call] directly. The identity * This call needs to be synchronous so we need it to return a [Call] directly. The identity
@ -66,23 +67,25 @@ interface UnauthenticatedIdentityApi {
): Call<RefreshTokenResponseJson> ): Call<RefreshTokenResponseJson>
@POST("/accounts/prelogin") @POST("/accounts/prelogin")
suspend fun preLogin(@Body body: PreLoginRequestJson): Result<PreLoginResponseJson> suspend fun preLogin(@Body body: PreLoginRequestJson): NetworkResult<PreLoginResponseJson>
@POST("/accounts/register") @POST("/accounts/register")
suspend fun register(@Body body: RegisterRequestJson): Result<RegisterResponseJson.Success> suspend fun register(
@Body body: RegisterRequestJson,
): NetworkResult<RegisterResponseJson.Success>
@POST("/accounts/register/finish") @POST("/accounts/register/finish")
suspend fun registerFinish( suspend fun registerFinish(
@Body body: RegisterFinishRequestJson, @Body body: RegisterFinishRequestJson,
): Result<RegisterResponseJson.Success> ): NetworkResult<RegisterResponseJson.Success>
@POST("/accounts/register/send-verification-email") @POST("/accounts/register/send-verification-email")
suspend fun sendVerificationEmail( suspend fun sendVerificationEmail(
@Body body: SendVerificationEmailRequestJson, @Body body: SendVerificationEmailRequestJson,
): Result<JsonPrimitive?> ): NetworkResult<JsonPrimitive?>
@POST("/accounts/register/verification-email-clicked") @POST("/accounts/register/verification-email-clicked")
suspend fun verifyEmailToken( suspend fun verifyEmailToken(
@Body body: VerifyEmailTokenRequestJson, @Body body: VerifyEmailTokenRequestJson,
): Result<Unit> ): NetworkResult<Unit>
} }

View file

@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
import androidx.annotation.Keep import androidx.annotation.Keep
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorMasterKeyResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_KEY_AUTHORIZATION
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
@ -20,11 +21,11 @@ interface UnauthenticatedKeyConnectorApi {
@Url url: String, @Url url: String,
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String, @Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
@Body body: KeyConnectorMasterKeyRequestJson, @Body body: KeyConnectorMasterKeyRequestJson,
): Result<Unit> ): NetworkResult<Unit>
@GET @GET
suspend fun getMasterKeyFromKeyConnector( suspend fun getMasterKeyFromKeyConnector(
@Url url: String, @Url url: String,
@Header(HEADER_KEY_AUTHORIZATION) bearerToken: String, @Header(HEADER_KEY_AUTHORIZATION) bearerToken: String,
): Result<KeyConnectorMasterKeyResponseJson> ): NetworkResult<KeyConnectorMasterKeyResponseJson>
} }

View file

@ -4,6 +4,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomain
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsRequest import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsRequest
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.POST import retrofit2.http.POST
@ -17,7 +18,7 @@ interface UnauthenticatedOrganizationApi {
@POST("/organizations/domain/sso/details") @POST("/organizations/domain/sso/details")
suspend fun getClaimedDomainOrganizationDetails( suspend fun getClaimedDomainOrganizationDetails(
@Body body: OrganizationDomainSsoDetailsRequestJson, @Body body: OrganizationDomainSsoDetailsRequestJson,
): Result<OrganizationDomainSsoDetailsResponseJson> ): NetworkResult<OrganizationDomainSsoDetailsResponseJson>
/** /**
* Checks for the verfied organization domains of an email for SSO purposes. * Checks for the verfied organization domains of an email for SSO purposes.
@ -25,5 +26,5 @@ interface UnauthenticatedOrganizationApi {
@POST("/organizations/domain/sso/verified") @POST("/organizations/domain/sso/verified")
suspend fun getVerifiedOrganizationDomainsByEmail( suspend fun getVerifiedOrganizationDomainsByEmail(
@Body body: VerifiedOrganizationDomainSsoDetailsRequest, @Body body: VerifiedOrganizationDomainSsoDetailsRequest,
): Result<VerifiedOrganizationDomainSsoDetailsResponse> ): NetworkResult<VerifiedOrganizationDomainSsoDetailsResponse>
} }

View file

@ -19,6 +19,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyOtpRequestJs
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_BEARER_PREFIX import com.x8bit.bitwarden.data.platform.datasource.network.util.HEADER_VALUE_BEARER_PREFIX
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
/** /**
@ -37,18 +38,22 @@ class AccountsServiceImpl(
* Converts the currently active account to a key-connector account. * Converts the currently active account to a key-connector account.
*/ */
override suspend fun convertToKeyConnector(): Result<Unit> = override suspend fun convertToKeyConnector(): Result<Unit> =
authenticatedAccountsApi.convertToKeyConnector() authenticatedAccountsApi
.convertToKeyConnector()
.toResult()
override suspend fun createAccountKeys( override suspend fun createAccountKeys(
publicKey: String, publicKey: String,
encryptedPrivateKey: String, encryptedPrivateKey: String,
): Result<Unit> = ): Result<Unit> =
authenticatedAccountsApi.createAccountKeys( authenticatedAccountsApi
.createAccountKeys(
body = CreateAccountKeysRequest( body = CreateAccountKeysRequest(
publicKey = publicKey, publicKey = publicKey,
encryptedPrivateKey = encryptedPrivateKey, encryptedPrivateKey = encryptedPrivateKey,
), ),
) )
.toResult()
override suspend fun deleteAccount( override suspend fun deleteAccount(
masterPasswordHash: String?, masterPasswordHash: String?,
@ -61,9 +66,8 @@ class AccountsServiceImpl(
oneTimePassword = oneTimePassword, oneTimePassword = oneTimePassword,
), ),
) )
.map { .toResult()
DeleteAccountResponseJson.Success .map { DeleteAccountResponseJson.Success }
}
.recoverCatching { throwable -> .recoverCatching { throwable ->
throwable throwable
.toBitwardenError() .toBitwardenError()
@ -75,20 +79,25 @@ class AccountsServiceImpl(
} }
override suspend fun requestOneTimePasscode(): Result<Unit> = override suspend fun requestOneTimePasscode(): Result<Unit> =
authenticatedAccountsApi.requestOtp() authenticatedAccountsApi
.requestOtp()
.toResult()
override suspend fun verifyOneTimePasscode(passcode: String): Result<Unit> = override suspend fun verifyOneTimePasscode(passcode: String): Result<Unit> =
authenticatedAccountsApi.verifyOtp( authenticatedAccountsApi
.verifyOtp(
VerifyOtpRequestJson( VerifyOtpRequestJson(
oneTimePasscode = passcode, oneTimePasscode = passcode,
), ),
) )
.toResult()
override suspend fun requestPasswordHint( override suspend fun requestPasswordHint(
email: String, email: String,
): Result<PasswordHintResponseJson> = ): Result<PasswordHintResponseJson> =
unauthenticatedAccountsApi unauthenticatedAccountsApi
.passwordHintRequest(PasswordHintRequestJson(email)) .passwordHintRequest(PasswordHintRequestJson(email))
.toResult()
.map { PasswordHintResponseJson.Success } .map { PasswordHintResponseJson.Success }
.recoverCatching { throwable -> .recoverCatching { throwable ->
throwable throwable
@ -101,54 +110,70 @@ class AccountsServiceImpl(
} }
override suspend fun resendVerificationCodeEmail(body: ResendEmailRequestJson): Result<Unit> = override suspend fun resendVerificationCodeEmail(body: ResendEmailRequestJson): Result<Unit> =
unauthenticatedAccountsApi.resendVerificationCodeEmail(body = body) unauthenticatedAccountsApi
.resendVerificationCodeEmail(body = body)
.toResult()
override suspend fun resetPassword(body: ResetPasswordRequestJson): Result<Unit> { override suspend fun resetPassword(body: ResetPasswordRequestJson): Result<Unit> =
return if (body.currentPasswordHash == null) { if (body.currentPasswordHash == null) {
authenticatedAccountsApi.resetTempPassword(body = body) authenticatedAccountsApi
.resetTempPassword(body = body)
.toResult()
} else { } else {
authenticatedAccountsApi.resetPassword(body = body) authenticatedAccountsApi
} .resetPassword(body = body)
.toResult()
} }
override suspend fun setKeyConnectorKey( override suspend fun setKeyConnectorKey(
accessToken: String, accessToken: String,
body: KeyConnectorKeyRequestJson, body: KeyConnectorKeyRequestJson,
): Result<Unit> = unauthenticatedAccountsApi.setKeyConnectorKey( ): Result<Unit> =
unauthenticatedAccountsApi
.setKeyConnectorKey(
body = body, body = body,
bearerToken = "$HEADER_VALUE_BEARER_PREFIX$accessToken", bearerToken = "$HEADER_VALUE_BEARER_PREFIX$accessToken",
) )
.toResult()
override suspend fun setPassword( override suspend fun setPassword(
body: SetPasswordRequestJson, body: SetPasswordRequestJson,
): Result<Unit> = authenticatedAccountsApi.setPassword(body) ): Result<Unit> = authenticatedAccountsApi
.setPassword(body)
.toResult()
override suspend fun getMasterKeyFromKeyConnector( override suspend fun getMasterKeyFromKeyConnector(
url: String, url: String,
accessToken: String, accessToken: String,
): Result<KeyConnectorMasterKeyResponseJson> = ): Result<KeyConnectorMasterKeyResponseJson> =
unauthenticatedKeyConnectorApi.getMasterKeyFromKeyConnector( unauthenticatedKeyConnectorApi
.getMasterKeyFromKeyConnector(
url = "$url/user-keys", url = "$url/user-keys",
bearerToken = "$HEADER_VALUE_BEARER_PREFIX$accessToken", bearerToken = "$HEADER_VALUE_BEARER_PREFIX$accessToken",
) )
.toResult()
override suspend fun storeMasterKeyToKeyConnector( override suspend fun storeMasterKeyToKeyConnector(
url: String, url: String,
masterKey: String, masterKey: String,
): Result<Unit> = ): Result<Unit> =
authenticatedKeyConnectorApi.storeMasterKeyToKeyConnector( authenticatedKeyConnectorApi
.storeMasterKeyToKeyConnector(
url = "$url/user-keys", url = "$url/user-keys",
body = KeyConnectorMasterKeyRequestJson(masterKey = masterKey), body = KeyConnectorMasterKeyRequestJson(masterKey = masterKey),
) )
.toResult()
override suspend fun storeMasterKeyToKeyConnector( override suspend fun storeMasterKeyToKeyConnector(
url: String, url: String,
accessToken: String, accessToken: String,
masterKey: String, masterKey: String,
): Result<Unit> = ): Result<Unit> =
unauthenticatedKeyConnectorApi.storeMasterKeyToKeyConnector( unauthenticatedKeyConnectorApi
.storeMasterKeyToKeyConnector(
url = "$url/user-keys", url = "$url/user-keys",
bearerToken = "$HEADER_VALUE_BEARER_PREFIX$accessToken", bearerToken = "$HEADER_VALUE_BEARER_PREFIX$accessToken",
body = KeyConnectorMasterKeyRequestJson(masterKey = masterKey), body = KeyConnectorMasterKeyRequestJson(masterKey = masterKey),
) )
.toResult()
} }

View file

@ -3,17 +3,22 @@ package com.x8bit.bitwarden.data.auth.datasource.network.service
import com.x8bit.bitwarden.data.auth.datasource.network.api.AuthenticatedAuthRequestsApi import com.x8bit.bitwarden.data.auth.datasource.network.api.AuthenticatedAuthRequestsApi
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestUpdateRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestUpdateRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
class AuthRequestsServiceImpl( class AuthRequestsServiceImpl(
private val authenticatedAuthRequestsApi: AuthenticatedAuthRequestsApi, private val authenticatedAuthRequestsApi: AuthenticatedAuthRequestsApi,
) : AuthRequestsService { ) : AuthRequestsService {
override suspend fun getAuthRequests(): Result<AuthRequestsResponseJson> = override suspend fun getAuthRequests(): Result<AuthRequestsResponseJson> =
authenticatedAuthRequestsApi.getAuthRequests() authenticatedAuthRequestsApi
.getAuthRequests()
.toResult()
override suspend fun getAuthRequest( override suspend fun getAuthRequest(
requestId: String, requestId: String,
): Result<AuthRequestsResponseJson.AuthRequest> = ): Result<AuthRequestsResponseJson.AuthRequest> =
authenticatedAuthRequestsApi.getAuthRequest(requestId = requestId) authenticatedAuthRequestsApi
.getAuthRequest(requestId = requestId)
.toResult()
override suspend fun updateAuthRequest( override suspend fun updateAuthRequest(
requestId: String, requestId: String,
@ -22,7 +27,8 @@ class AuthRequestsServiceImpl(
deviceId: String, deviceId: String,
isApproved: Boolean, isApproved: Boolean,
): Result<AuthRequestsResponseJson.AuthRequest> = ): Result<AuthRequestsResponseJson.AuthRequest> =
authenticatedAuthRequestsApi.updateAuthRequest( authenticatedAuthRequestsApi
.updateAuthRequest(
userId = requestId, userId = requestId,
body = AuthRequestUpdateRequestJson( body = AuthRequestUpdateRequestJson(
key = key, key = key,
@ -31,4 +37,5 @@ class AuthRequestsServiceImpl(
isApproved = isApproved, isApproved = isApproved,
), ),
) )
.toResult()
} }

View file

@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.api.UnauthenticatedDevic
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceKeysResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
class DevicesServiceImpl( class DevicesServiceImpl(
private val authenticatedDevicesApi: AuthenticatedDevicesApi, private val authenticatedDevicesApi: AuthenticatedDevicesApi,
@ -13,17 +14,20 @@ class DevicesServiceImpl(
override suspend fun getIsKnownDevice( override suspend fun getIsKnownDevice(
emailAddress: String, emailAddress: String,
deviceId: String, deviceId: String,
): Result<Boolean> = unauthenticatedDevicesApi.getIsKnownDevice( ): Result<Boolean> = unauthenticatedDevicesApi
.getIsKnownDevice(
emailAddress = emailAddress.base64UrlEncode(), emailAddress = emailAddress.base64UrlEncode(),
deviceId = deviceId, deviceId = deviceId,
) )
.toResult()
override suspend fun trustDevice( override suspend fun trustDevice(
appId: String, appId: String,
encryptedUserKey: String, encryptedUserKey: String,
encryptedDevicePublicKey: String, encryptedDevicePublicKey: String,
encryptedDevicePrivateKey: String, encryptedDevicePrivateKey: String,
): Result<TrustedDeviceKeysResponseJson> = authenticatedDevicesApi.updateTrustedDeviceKeys( ): Result<TrustedDeviceKeysResponseJson> = authenticatedDevicesApi
.updateTrustedDeviceKeys(
appId = appId, appId = appId,
request = TrustedDeviceKeysRequestJson( request = TrustedDeviceKeysRequestJson(
encryptedUserKey = encryptedUserKey, encryptedUserKey = encryptedUserKey,
@ -31,4 +35,5 @@ class DevicesServiceImpl(
encryptedDevicePrivateKey = encryptedDevicePrivateKey, encryptedDevicePrivateKey = encryptedDevicePrivateKey,
), ),
) )
.toResult()
} }

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.auth.datasource.network.service package com.x8bit.bitwarden.data.auth.datasource.network.service
import com.x8bit.bitwarden.data.auth.datasource.network.api.HaveIBeenPwnedApi import com.x8bit.bitwarden.data.auth.datasource.network.api.HaveIBeenPwnedApi
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
import java.security.MessageDigest import java.security.MessageDigest
class HaveIBeenPwnedServiceImpl(private val api: HaveIBeenPwnedApi) : HaveIBeenPwnedService { class HaveIBeenPwnedServiceImpl(private val api: HaveIBeenPwnedApi) : HaveIBeenPwnedService {
@ -17,6 +18,7 @@ class HaveIBeenPwnedServiceImpl(private val api: HaveIBeenPwnedApi) : HaveIBeenP
return api return api
.fetchBreachedPasswords(hashPrefix = hashPrefix) .fetchBreachedPasswords(hashPrefix = hashPrefix)
.toResult()
.mapCatching { responseBody -> .mapCatching { responseBody ->
responseBody.string() responseBody.string()
// First split the response by newline: each hashed password is on a new line. // First split the response by newline: each hashed password is on a new line.

View file

@ -16,8 +16,9 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRe
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
import com.x8bit.bitwarden.data.platform.datasource.network.util.executeForResult import com.x8bit.bitwarden.data.platform.datasource.network.util.executeForNetworkResult
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
import com.x8bit.bitwarden.data.platform.util.DeviceModelProvider import com.x8bit.bitwarden.data.platform.util.DeviceModelProvider
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -28,12 +29,15 @@ class IdentityServiceImpl(
) : IdentityService { ) : IdentityService {
override suspend fun preLogin(email: String): Result<PreLoginResponseJson> = override suspend fun preLogin(email: String): Result<PreLoginResponseJson> =
unauthenticatedIdentityApi.preLogin(PreLoginRequestJson(email = email)) unauthenticatedIdentityApi
.preLogin(PreLoginRequestJson(email = email))
.toResult()
@Suppress("MagicNumber") @Suppress("MagicNumber")
override suspend fun register(body: RegisterRequestJson): Result<RegisterResponseJson> = override suspend fun register(body: RegisterRequestJson): Result<RegisterResponseJson> =
unauthenticatedIdentityApi unauthenticatedIdentityApi
.register(body) .register(body)
.toResult()
.recoverCatching { throwable -> .recoverCatching { throwable ->
val bitwardenError = throwable.toBitwardenError() val bitwardenError = throwable.toBitwardenError()
bitwardenError bitwardenError
@ -75,6 +79,7 @@ class IdentityServiceImpl(
captchaResponse = captchaToken, captchaResponse = captchaToken,
authRequestId = authModel.authRequestId, authRequestId = authModel.authRequestId,
) )
.toResult()
.recoverCatching { throwable -> .recoverCatching { throwable ->
val bitwardenError = throwable.toBitwardenError() val bitwardenError = throwable.toBitwardenError()
bitwardenError.parseErrorBodyOrNull<GetTokenResponseJson.CaptchaRequired>( bitwardenError.parseErrorBodyOrNull<GetTokenResponseJson.CaptchaRequired>(
@ -95,6 +100,7 @@ class IdentityServiceImpl(
.prevalidateSso( .prevalidateSso(
organizationIdentifier = organizationIdentifier, organizationIdentifier = organizationIdentifier,
) )
.toResult()
override fun refreshTokenSynchronously( override fun refreshTokenSynchronously(
refreshToken: String, refreshToken: String,
@ -104,7 +110,8 @@ class IdentityServiceImpl(
grantType = "refresh_token", grantType = "refresh_token",
refreshToken = refreshToken, refreshToken = refreshToken,
) )
.executeForResult() .executeForNetworkResult()
.toResult()
@Suppress("MagicNumber") @Suppress("MagicNumber")
override suspend fun registerFinish( override suspend fun registerFinish(
@ -112,6 +119,7 @@ class IdentityServiceImpl(
): Result<RegisterResponseJson> = ): Result<RegisterResponseJson> =
unauthenticatedIdentityApi unauthenticatedIdentityApi
.registerFinish(body) .registerFinish(body)
.toResult()
.recoverCatching { throwable -> .recoverCatching { throwable ->
val bitwardenError = throwable.toBitwardenError() val bitwardenError = throwable.toBitwardenError()
bitwardenError bitwardenError
@ -127,6 +135,7 @@ class IdentityServiceImpl(
): Result<String?> { ): Result<String?> {
return unauthenticatedIdentityApi return unauthenticatedIdentityApi
.sendVerificationEmail(body = body) .sendVerificationEmail(body = body)
.toResult()
.map { it?.content } .map { it?.content }
} }
@ -136,9 +145,8 @@ class IdentityServiceImpl(
.verifyEmailToken( .verifyEmailToken(
body = body, body = body,
) )
.map { .toResult()
VerifyEmailTokenResponseJson.Valid .map { VerifyEmailTokenResponseJson.Valid }
}
.recoverCatching { throwable -> .recoverCatching { throwable ->
val bitwardenError = throwable.toBitwardenError() val bitwardenError = throwable.toBitwardenError()
bitwardenError bitwardenError

View file

@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.api.UnauthenticatedAuthR
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestTypeJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson import com.x8bit.bitwarden.data.auth.datasource.network.model.AuthRequestsResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
import com.x8bit.bitwarden.data.platform.util.asFailure import com.x8bit.bitwarden.data.platform.util.asFailure
/** /**
@ -24,7 +25,8 @@ class NewAuthRequestServiceImpl(
): Result<AuthRequestsResponseJson.AuthRequest> = ): Result<AuthRequestsResponseJson.AuthRequest> =
when (authRequestType) { when (authRequestType) {
AuthRequestTypeJson.LOGIN_WITH_DEVICE -> { AuthRequestTypeJson.LOGIN_WITH_DEVICE -> {
unauthenticatedAuthRequestsApi.createAuthRequest( unauthenticatedAuthRequestsApi
.createAuthRequest(
deviceIdentifier = deviceId, deviceIdentifier = deviceId,
body = AuthRequestRequestJson( body = AuthRequestRequestJson(
email = email, email = email,
@ -35,6 +37,7 @@ class NewAuthRequestServiceImpl(
type = authRequestType, type = authRequestType,
), ),
) )
.toResult()
} }
AuthRequestTypeJson.UNLOCK -> { AuthRequestTypeJson.UNLOCK -> {
@ -43,7 +46,8 @@ class NewAuthRequestServiceImpl(
} }
AuthRequestTypeJson.ADMIN_APPROVAL -> { AuthRequestTypeJson.ADMIN_APPROVAL -> {
authenticatedAuthRequestsApi.createAdminAuthRequest( authenticatedAuthRequestsApi
.createAdminAuthRequest(
deviceIdentifier = deviceId, deviceIdentifier = deviceId,
body = AuthRequestRequestJson( body = AuthRequestRequestJson(
email = email, email = email,
@ -54,6 +58,7 @@ class NewAuthRequestServiceImpl(
type = authRequestType, type = authRequestType,
), ),
) )
.toResult()
} }
} }
@ -63,11 +68,15 @@ class NewAuthRequestServiceImpl(
isSso: Boolean, isSso: Boolean,
): Result<AuthRequestsResponseJson.AuthRequest> = ): Result<AuthRequestsResponseJson.AuthRequest> =
if (isSso) { if (isSso) {
authenticatedAuthRequestsApi.getAuthRequest(requestId) authenticatedAuthRequestsApi
.getAuthRequest(requestId = requestId)
.toResult()
} else { } else {
unauthenticatedAuthRequestsApi.getAuthRequestUpdate( unauthenticatedAuthRequestsApi
.getAuthRequestUpdate(
requestId = requestId, requestId = requestId,
accessCode = accessCode, accessCode = accessCode,
) )
.toResult()
} }
} }

View file

@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysRe
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationResetPasswordEnrollRequestJson import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationResetPasswordEnrollRequestJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsRequest import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsRequest
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
/** /**
* Default implementation of [OrganizationService]. * Default implementation of [OrganizationService].
@ -31,6 +32,7 @@ class OrganizationServiceImpl(
resetPasswordKey = resetPasswordKey, resetPasswordKey = resetPasswordKey,
), ),
) )
.toResult()
override suspend fun getOrganizationDomainSsoDetails( override suspend fun getOrganizationDomainSsoDetails(
email: String, email: String,
@ -40,6 +42,7 @@ class OrganizationServiceImpl(
email = email, email = email,
), ),
) )
.toResult()
override suspend fun getOrganizationAutoEnrollStatus( override suspend fun getOrganizationAutoEnrollStatus(
organizationIdentifier: String, organizationIdentifier: String,
@ -47,6 +50,7 @@ class OrganizationServiceImpl(
.getOrganizationAutoEnrollResponse( .getOrganizationAutoEnrollResponse(
organizationIdentifier = organizationIdentifier, organizationIdentifier = organizationIdentifier,
) )
.toResult()
override suspend fun getOrganizationKeys( override suspend fun getOrganizationKeys(
organizationId: String, organizationId: String,
@ -54,6 +58,7 @@ class OrganizationServiceImpl(
.getOrganizationKeys( .getOrganizationKeys(
organizationId = organizationId, organizationId = organizationId,
) )
.toResult()
override suspend fun getVerifiedOrganizationDomainSsoDetails( override suspend fun getVerifiedOrganizationDomainSsoDetails(
email: String, email: String,
@ -63,4 +68,5 @@ class OrganizationServiceImpl(
email = email, email = email,
), ),
) )
.toResult()
} }

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.api package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.api
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Url import retrofit2.http.Url
@ -15,5 +16,5 @@ interface DigitalAssetLinkApi {
@GET @GET
suspend fun getDigitalAssetLinks( suspend fun getDigitalAssetLinks(
@Url url: String, @Url url: String,
): Result<List<DigitalAssetLinkResponseJson>> ): NetworkResult<List<DigitalAssetLinkResponseJson>>
} }

View file

@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.service
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.api.DigitalAssetLinkApi import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.api.DigitalAssetLinkApi
import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson import com.x8bit.bitwarden.data.autofill.fido2.datasource.network.model.DigitalAssetLinkResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
/** /**
* Primary implementation of [DigitalAssetLinkService]. * Primary implementation of [DigitalAssetLinkService].
@ -18,4 +19,5 @@ class DigitalAssetLinkServiceImpl(
.getDigitalAssetLinks( .getDigitalAssetLinks(
url = "$scheme$relyingParty/.well-known/assetlinks.json", url = "$scheme$relyingParty/.well-known/assetlinks.json",
) )
.toResult()
} }

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.platform.datasource.network.api package com.x8bit.bitwarden.data.platform.datasource.network.api
import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.http.GET import retrofit2.http.GET
/** /**
@ -9,5 +10,5 @@ import retrofit2.http.GET
interface ConfigApi { interface ConfigApi {
@GET("config") @GET("config")
suspend fun getConfig(): Result<ConfigResponseJson> suspend fun getConfig(): NetworkResult<ConfigResponseJson>
} }

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.network.api package com.x8bit.bitwarden.data.platform.datasource.network.api
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEventJson import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEventJson
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.POST import retrofit2.http.POST
@ -9,5 +10,7 @@ import retrofit2.http.POST
*/ */
interface EventApi { interface EventApi {
@POST("/collect") @POST("/collect")
suspend fun collectOrganizationEvents(@Body events: List<OrganizationEventJson>): Result<Unit> suspend fun collectOrganizationEvents(
@Body events: List<OrganizationEventJson>,
): NetworkResult<Unit>
} }

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.network.api package com.x8bit.bitwarden.data.platform.datasource.network.api
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenRequest import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenRequest
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.PUT import retrofit2.http.PUT
@ -13,5 +14,5 @@ interface PushApi {
suspend fun putDeviceToken( suspend fun putDeviceToken(
@Path("appId") appId: String, @Path("appId") appId: String,
@Body body: PushTokenRequest, @Body body: PushTokenRequest,
): Result<Unit> ): NetworkResult<Unit>
} }

View file

@ -1,7 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.network.core package com.x8bit.bitwarden.data.platform.datasource.network.core
import com.x8bit.bitwarden.data.platform.util.asFailure import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.platform.util.asSuccess
import okhttp3.Request import okhttp3.Request
import okio.IOException import okio.IOException
import okio.Timeout import okio.Timeout
@ -18,33 +17,36 @@ import java.lang.reflect.Type
private const val NO_CONTENT_RESPONSE_CODE: Int = 204 private const val NO_CONTENT_RESPONSE_CODE: Int = 204
/** /**
* A [Call] for wrapping a network request into a [Result]. * A [Call] for wrapping a network request into a [NetworkResult].
*/ */
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
class ResultCall<T>( class NetworkResultCall<T>(
private val backingCall: Call<T>, private val backingCall: Call<T>,
private val successType: Type, private val successType: Type,
) : Call<Result<T>> { ) : Call<NetworkResult<T>> {
override fun cancel(): Unit = backingCall.cancel() override fun cancel(): Unit = backingCall.cancel()
override fun clone(): Call<Result<T>> = ResultCall(backingCall, successType) override fun clone(): Call<NetworkResult<T>> = NetworkResultCall(backingCall, successType)
override fun enqueue(callback: Callback<Result<T>>): Unit = backingCall.enqueue( override fun enqueue(callback: Callback<NetworkResult<T>>): Unit = backingCall.enqueue(
object : Callback<T> { object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) { override fun onResponse(call: Call<T>, response: Response<T>) {
callback.onResponse(this@ResultCall, Response.success(response.toResult())) callback.onResponse(
this@NetworkResultCall,
Response.success(response.toNetworkResult()),
)
} }
override fun onFailure(call: Call<T>, t: Throwable) { override fun onFailure(call: Call<T>, t: Throwable) {
callback.onResponse(this@ResultCall, Response.success(t.toFailure())) callback.onResponse(this@NetworkResultCall, Response.success(t.toFailure()))
} }
}, },
) )
@Suppress("TooGenericExceptionCaught") @Suppress("TooGenericExceptionCaught")
override fun execute(): Response<Result<T>> = override fun execute(): Response<NetworkResult<T>> =
try { try {
Response.success(backingCall.execute().toResult()) Response.success(backingCall.execute().toNetworkResult())
} catch (ioException: IOException) { } catch (ioException: IOException) {
Response.success(ioException.toFailure()) Response.success(ioException.toFailure())
} catch (runtimeException: RuntimeException) { } catch (runtimeException: RuntimeException) {
@ -60,20 +62,18 @@ class ResultCall<T>(
override fun timeout(): Timeout = backingCall.timeout() override fun timeout(): Timeout = backingCall.timeout()
/** /**
* Synchronously send the request and return its response as a [Result]. * Synchronously send the request and return its response as a [NetworkResult].
*/ */
fun executeForResult(): Result<T> = requireNotNull(execute().body()) fun executeForResult(): NetworkResult<T> = requireNotNull(execute().body())
private fun Throwable.toFailure(): Result<T> = private fun Throwable.toFailure(): NetworkResult<T> {
this
.also {
// We rebuild the URL without query params, we do not want to log those // We rebuild the URL without query params, we do not want to log those
val url = backingCall.request().url.toUrl().run { "$protocol://$authority$path" } val url = backingCall.request().url.toUrl().run { "$protocol://$authority$path" }
Timber.w(it, "Network Error: $url") Timber.w(this, "Network Error: $url")
return NetworkResult.Failure(this)
} }
.asFailure()
private fun Response<T>.toResult(): Result<T> = private fun Response<T>.toNetworkResult(): NetworkResult<T> =
if (!this.isSuccessful) { if (!this.isSuccessful) {
HttpException(this).toFailure() HttpException(this).toFailure()
} else { } else {
@ -81,11 +81,11 @@ class ResultCall<T>(
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
when { when {
// We got a nonnull T as the body, just return it. // We got a nonnull T as the body, just return it.
body != null -> body.asSuccess() body != null -> NetworkResult.Success(body)
// We expected the body to be null since the successType is Unit, just return Unit. // We expected the body to be null since the successType is Unit, just return Unit.
successType == Unit::class.java -> (Unit as T).asSuccess() successType == Unit::class.java -> NetworkResult.Success(Unit as T)
// We allow null for 204's, just return null. // We allow null for 204's, just return null.
this.code() == NO_CONTENT_RESPONSE_CODE -> (null as T).asSuccess() this.code() == NO_CONTENT_RESPONSE_CODE -> NetworkResult.Success(null as T)
// All other null bodies result in an error. // All other null bodies result in an error.
else -> IllegalStateException("Unexpected null body!").toFailure() else -> IllegalStateException("Unexpected null body!").toFailure()
} }

View file

@ -0,0 +1,17 @@
package com.x8bit.bitwarden.data.platform.datasource.network.core
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.Call
import retrofit2.CallAdapter
import java.lang.reflect.Type
/**
* A [CallAdapter] for wrapping network requests into [NetworkResult].
*/
class NetworkResultCallAdapter<T>(
private val successType: Type,
) : CallAdapter<T, Call<NetworkResult<T>>> {
override fun responseType(): Type = successType
override fun adapt(call: Call<T>): Call<NetworkResult<T>> = NetworkResultCall(call, successType)
}

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.network.core package com.x8bit.bitwarden.data.platform.datasource.network.core
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.Call import retrofit2.Call
import retrofit2.CallAdapter import retrofit2.CallAdapter
import retrofit2.Retrofit import retrofit2.Retrofit
@ -7,9 +8,9 @@ import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type import java.lang.reflect.Type
/** /**
* A [CallAdapter.Factory] for wrapping network requests into [kotlin.Result]. * A [CallAdapter.Factory] for wrapping network requests into [NetworkResult].
*/ */
class ResultCallAdapterFactory : CallAdapter.Factory() { class NetworkResultCallAdapterFactory : CallAdapter.Factory() {
override fun get( override fun get(
returnType: Type, returnType: Type,
annotations: Array<out Annotation>, annotations: Array<out Annotation>,
@ -18,13 +19,13 @@ class ResultCallAdapterFactory : CallAdapter.Factory() {
check(returnType is ParameterizedType) { "$returnType must be parameterized" } check(returnType is ParameterizedType) { "$returnType must be parameterized" }
val containerType = getParameterUpperBound(0, returnType) val containerType = getParameterUpperBound(0, returnType)
if (getRawType(containerType) != Result::class.java) return null if (getRawType(containerType) != NetworkResult::class.java) return null
check(containerType is ParameterizedType) { "$containerType must be parameterized" } check(containerType is ParameterizedType) { "$containerType must be parameterized" }
val requestType = getParameterUpperBound(0, containerType) val requestType = getParameterUpperBound(0, containerType)
return if (getRawType(returnType) == Call::class.java) { return if (getRawType(returnType) == Call::class.java) {
ResultCallAdapter<Any>(successType = requestType) NetworkResultCallAdapter<Any>(successType = requestType)
} else { } else {
null null
} }

View file

@ -1,16 +0,0 @@
package com.x8bit.bitwarden.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<T>(
private val successType: Type,
) : CallAdapter<T, Call<Result<T>>> {
override fun responseType(): Type = successType
override fun adapt(call: Call<T>): Call<Result<T>> = ResultCall(call, successType)
}

View file

@ -0,0 +1,18 @@
package com.x8bit.bitwarden.data.platform.datasource.network.model
/**
* A wrapper class for a network result for type [T]. If the network request is successful, the
* response will be a [Success] containing the data. If the network request is a failure, the
* response will be a [Failure] containing the [Throwable].
*/
sealed class NetworkResult<out T> {
/**
* A successful network result with the relevant [T] data.
*/
data class Success<T>(val value: T) : NetworkResult<T>()
/**
* A failed network result with the relevant [throwable] error.
*/
data class Failure(val throwable: Throwable) : NetworkResult<Nothing>()
}

View file

@ -1,7 +1,7 @@
package com.x8bit.bitwarden.data.platform.datasource.network.retrofit package com.x8bit.bitwarden.data.platform.datasource.network.retrofit
import com.x8bit.bitwarden.data.platform.datasource.network.authenticator.RefreshAuthenticator import com.x8bit.bitwarden.data.platform.datasource.network.authenticator.RefreshAuthenticator
import com.x8bit.bitwarden.data.platform.datasource.network.core.ResultCallAdapterFactory import com.x8bit.bitwarden.data.platform.datasource.network.core.NetworkResultCallAdapterFactory
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptor import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors
@ -105,7 +105,7 @@ class RetrofitsImpl(
private val baseRetrofitBuilder: Retrofit.Builder by lazy { private val baseRetrofitBuilder: Retrofit.Builder by lazy {
Retrofit.Builder() Retrofit.Builder()
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.addCallAdapterFactory(ResultCallAdapterFactory()) .addCallAdapterFactory(NetworkResultCallAdapterFactory())
.client(baseOkHttpClient) .client(baseOkHttpClient)
} }

View file

@ -2,7 +2,8 @@ package com.x8bit.bitwarden.data.platform.datasource.network.service
import com.x8bit.bitwarden.data.platform.datasource.network.api.ConfigApi import com.x8bit.bitwarden.data.platform.datasource.network.api.ConfigApi
import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson import com.x8bit.bitwarden.data.platform.datasource.network.model.ConfigResponseJson
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
class ConfigServiceImpl(private val configApi: ConfigApi) : ConfigService { class ConfigServiceImpl(private val configApi: ConfigApi) : ConfigService {
override suspend fun getConfig(): Result<ConfigResponseJson> = configApi.getConfig() override suspend fun getConfig(): Result<ConfigResponseJson> = configApi.getConfig().toResult()
} }

View file

@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.platform.datasource.network.service
import com.x8bit.bitwarden.data.platform.datasource.network.api.EventApi import com.x8bit.bitwarden.data.platform.datasource.network.api.EventApi
import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEventJson import com.x8bit.bitwarden.data.platform.datasource.network.model.OrganizationEventJson
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
/** /**
* The default implementation of the [EventService]. * The default implementation of the [EventService].
@ -11,5 +12,5 @@ class EventServiceImpl(
) : EventService { ) : EventService {
override suspend fun sendOrganizationEvents( override suspend fun sendOrganizationEvents(
events: List<OrganizationEventJson>, events: List<OrganizationEventJson>,
): Result<Unit> = eventApi.collectOrganizationEvents(events = events) ): Result<Unit> = eventApi.collectOrganizationEvents(events = events).toResult()
} }

View file

@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.platform.datasource.network.service
import com.x8bit.bitwarden.data.platform.datasource.network.api.PushApi import com.x8bit.bitwarden.data.platform.datasource.network.api.PushApi
import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenRequest import com.x8bit.bitwarden.data.platform.datasource.network.model.PushTokenRequest
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
class PushServiceImpl( class PushServiceImpl(
private val pushApi: PushApi, private val pushApi: PushApi,
@ -15,4 +16,5 @@ class PushServiceImpl(
appId = appId, appId = appId,
body = body, body = body,
) )
.toResult()
} }

View file

@ -1,21 +1,22 @@
package com.x8bit.bitwarden.data.platform.datasource.network.util package com.x8bit.bitwarden.data.platform.datasource.network.util
import com.x8bit.bitwarden.data.platform.datasource.network.core.ResultCall import com.x8bit.bitwarden.data.platform.datasource.network.core.NetworkResultCall
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import retrofit2.Call import retrofit2.Call
/** /**
* Synchronously executes the [Call] and returns the [Result]. * Synchronously executes the [Call] and returns the [NetworkResult].
*/ */
inline fun <reified T : Any> Call<T>.executeForResult(): Result<T> = inline fun <reified T : Any> Call<T>.executeForNetworkResult(): NetworkResult<T> =
this this
.toResultCall() .toNetworkResultCall()
.executeForResult() .executeForResult()
/** /**
* Wraps the existing [Call] in a [ResultCall]. * Wraps the existing [Call] in a [NetworkResultCall].
*/ */
inline fun <reified T : Any> Call<T>.toResultCall(): ResultCall<T> = inline fun <reified T : Any> Call<T>.toNetworkResultCall(): NetworkResultCall<T> =
ResultCall( NetworkResultCall(
backingCall = this, backingCall = this,
successType = T::class.java, successType = T::class.java,
) )

View file

@ -0,0 +1,14 @@
package com.x8bit.bitwarden.data.platform.datasource.network.util
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.platform.util.asFailure
import com.x8bit.bitwarden.data.platform.util.asSuccess
/**
* Converts the [NetworkResult] to a [Result].
*/
fun <T> NetworkResult<T>.toResult(): Result<T> =
when (this) {
is NetworkResult.Failure -> this.throwable.asFailure()
is NetworkResult.Success -> this.value.asSuccess()
}

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.vault.datasource.network.api package com.x8bit.bitwarden.data.vault.datasource.network.api
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import okhttp3.RequestBody import okhttp3.RequestBody
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.Header import retrofit2.http.Header
@ -21,5 +22,5 @@ interface AzureApi {
@Header("x-ms-date") date: String, @Header("x-ms-date") date: String,
@Header("x-ms-version") version: String?, @Header("x-ms-version") version: String?,
@Body body: RequestBody, @Body body: RequestBody,
): Result<Unit> ): NetworkResult<Unit>
} }

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.vault.datasource.network.api package com.x8bit.bitwarden.data.vault.datasource.network.api
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonResponse import com.x8bit.bitwarden.data.vault.datasource.network.model.AttachmentJsonResponse
import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.CipherJsonRequest
@ -26,7 +27,7 @@ interface CiphersApi {
* Create a cipher. * Create a cipher.
*/ */
@POST("ciphers") @POST("ciphers")
suspend fun createCipher(@Body body: CipherJsonRequest): Result<SyncResponseJson.Cipher> suspend fun createCipher(@Body body: CipherJsonRequest): NetworkResult<SyncResponseJson.Cipher>
/** /**
* Create a cipher that belongs to an organization. * Create a cipher that belongs to an organization.
@ -34,7 +35,7 @@ interface CiphersApi {
@POST("ciphers/create") @POST("ciphers/create")
suspend fun createCipherInOrganization( suspend fun createCipherInOrganization(
@Body body: CreateCipherInOrganizationJsonRequest, @Body body: CreateCipherInOrganizationJsonRequest,
): Result<SyncResponseJson.Cipher> ): NetworkResult<SyncResponseJson.Cipher>
/** /**
* Associates an attachment with a cipher. * Associates an attachment with a cipher.
@ -43,7 +44,7 @@ interface CiphersApi {
suspend fun createAttachment( suspend fun createAttachment(
@Path("cipherId") cipherId: String, @Path("cipherId") cipherId: String,
@Body body: AttachmentJsonRequest, @Body body: AttachmentJsonRequest,
): Result<AttachmentJsonResponse> ): NetworkResult<AttachmentJsonResponse>
/** /**
* Uploads the attachment associated with a cipher. * Uploads the attachment associated with a cipher.
@ -53,7 +54,7 @@ interface CiphersApi {
@Path("cipherId") cipherId: String, @Path("cipherId") cipherId: String,
@Path("attachmentId") attachmentId: String, @Path("attachmentId") attachmentId: String,
@Body body: MultipartBody, @Body body: MultipartBody,
): Result<Unit> ): NetworkResult<Unit>
/** /**
* Updates a cipher. * Updates a cipher.
@ -62,7 +63,7 @@ interface CiphersApi {
suspend fun updateCipher( suspend fun updateCipher(
@Path("cipherId") cipherId: String, @Path("cipherId") cipherId: String,
@Body body: CipherJsonRequest, @Body body: CipherJsonRequest,
): Result<SyncResponseJson.Cipher> ): NetworkResult<SyncResponseJson.Cipher>
/** /**
* Shares a cipher. * Shares a cipher.
@ -71,7 +72,7 @@ interface CiphersApi {
suspend fun shareCipher( suspend fun shareCipher(
@Path("cipherId") cipherId: String, @Path("cipherId") cipherId: String,
@Body body: ShareCipherJsonRequest, @Body body: ShareCipherJsonRequest,
): Result<SyncResponseJson.Cipher> ): NetworkResult<SyncResponseJson.Cipher>
/** /**
* Shares an attachment. * Shares an attachment.
@ -82,7 +83,7 @@ interface CiphersApi {
@Path("attachmentId") attachmentId: String, @Path("attachmentId") attachmentId: String,
@Query("organizationId") organizationId: String?, @Query("organizationId") organizationId: String?,
@Body body: MultipartBody, @Body body: MultipartBody,
): Result<Unit> ): NetworkResult<Unit>
/** /**
* Updates a cipher's collections. * Updates a cipher's collections.
@ -91,7 +92,7 @@ interface CiphersApi {
suspend fun updateCipherCollections( suspend fun updateCipherCollections(
@Path("cipherId") cipherId: String, @Path("cipherId") cipherId: String,
@Body body: UpdateCipherCollectionsJsonRequest, @Body body: UpdateCipherCollectionsJsonRequest,
): Result<Unit> ): NetworkResult<Unit>
/** /**
* Hard deletes a cipher. * Hard deletes a cipher.
@ -99,7 +100,7 @@ interface CiphersApi {
@DELETE("ciphers/{cipherId}") @DELETE("ciphers/{cipherId}")
suspend fun hardDeleteCipher( suspend fun hardDeleteCipher(
@Path("cipherId") cipherId: String, @Path("cipherId") cipherId: String,
): Result<Unit> ): NetworkResult<Unit>
/** /**
* Soft deletes a cipher. * Soft deletes a cipher.
@ -107,7 +108,7 @@ interface CiphersApi {
@PUT("ciphers/{cipherId}/delete") @PUT("ciphers/{cipherId}/delete")
suspend fun softDeleteCipher( suspend fun softDeleteCipher(
@Path("cipherId") cipherId: String, @Path("cipherId") cipherId: String,
): Result<Unit> ): NetworkResult<Unit>
/** /**
* Deletes an attachment from a cipher. * Deletes an attachment from a cipher.
@ -116,7 +117,7 @@ interface CiphersApi {
suspend fun deleteCipherAttachment( suspend fun deleteCipherAttachment(
@Path("cipherId") cipherId: String, @Path("cipherId") cipherId: String,
@Path("attachmentId") attachmentId: String, @Path("attachmentId") attachmentId: String,
): Result<Unit> ): NetworkResult<Unit>
/** /**
* Restores a cipher. * Restores a cipher.
@ -124,7 +125,7 @@ interface CiphersApi {
@PUT("ciphers/{cipherId}/restore") @PUT("ciphers/{cipherId}/restore")
suspend fun restoreCipher( suspend fun restoreCipher(
@Path("cipherId") cipherId: String, @Path("cipherId") cipherId: String,
): Result<SyncResponseJson.Cipher> ): NetworkResult<SyncResponseJson.Cipher>
/** /**
* Gets a cipher. * Gets a cipher.
@ -132,7 +133,7 @@ interface CiphersApi {
@GET("ciphers/{cipherId}") @GET("ciphers/{cipherId}")
suspend fun getCipher( suspend fun getCipher(
@Path("cipherId") cipherId: String, @Path("cipherId") cipherId: String,
): Result<SyncResponseJson.Cipher> ): NetworkResult<SyncResponseJson.Cipher>
/** /**
* Gets a cipher attachment. * Gets a cipher attachment.
@ -141,11 +142,11 @@ interface CiphersApi {
suspend fun getCipherAttachment( suspend fun getCipherAttachment(
@Path("cipherId") cipherId: String, @Path("cipherId") cipherId: String,
@Path("attachmentId") attachmentId: String, @Path("attachmentId") attachmentId: String,
): Result<SyncResponseJson.Cipher.Attachment> ): NetworkResult<SyncResponseJson.Cipher.Attachment>
/** /**
* Indicates if the active user has unassigned ciphers. * Indicates if the active user has unassigned ciphers.
*/ */
@GET("ciphers/has-unassigned-ciphers") @GET("ciphers/has-unassigned-ciphers")
suspend fun hasUnassignedCiphers(): Result<Boolean> suspend fun hasUnassignedCiphers(): NetworkResult<Boolean>
} }

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.vault.datasource.network.api package com.x8bit.bitwarden.data.vault.datasource.network.api
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import okhttp3.ResponseBody import okhttp3.ResponseBody
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Streaming import retrofit2.http.Streaming
@ -16,5 +17,5 @@ interface DownloadApi {
@Streaming @Streaming
suspend fun getDataStream( suspend fun getDataStream(
@Url url: String, @Url url: String,
): Result<ResponseBody> ): NetworkResult<ResponseBody>
} }

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.vault.datasource.network.api package com.x8bit.bitwarden.data.vault.datasource.network.api
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import retrofit2.http.Body import retrofit2.http.Body
@ -18,7 +19,7 @@ interface FoldersApi {
* Create a folder. * Create a folder.
*/ */
@POST("folders") @POST("folders")
suspend fun createFolder(@Body body: FolderJsonRequest): Result<SyncResponseJson.Folder> suspend fun createFolder(@Body body: FolderJsonRequest): NetworkResult<SyncResponseJson.Folder>
/** /**
* Gets a folder. * Gets a folder.
@ -26,7 +27,7 @@ interface FoldersApi {
@GET("folders/{folderId}") @GET("folders/{folderId}")
suspend fun getFolder( suspend fun getFolder(
@Path("folderId") folderId: String, @Path("folderId") folderId: String,
): Result<SyncResponseJson.Folder> ): NetworkResult<SyncResponseJson.Folder>
/** /**
* Updates a folder. * Updates a folder.
@ -35,11 +36,11 @@ interface FoldersApi {
suspend fun updateFolder( suspend fun updateFolder(
@Path("folderId") folderId: String, @Path("folderId") folderId: String,
@Body body: FolderJsonRequest, @Body body: FolderJsonRequest,
): Result<SyncResponseJson.Folder> ): NetworkResult<SyncResponseJson.Folder>
/** /**
* Deletes a folder. * Deletes a folder.
*/ */
@DELETE("folders/{folderId}") @DELETE("folders/{folderId}")
suspend fun deleteFolder(@Path("folderId") folderId: String): Result<Unit> suspend fun deleteFolder(@Path("folderId") folderId: String): NetworkResult<Unit>
} }

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.data.vault.datasource.network.api package com.x8bit.bitwarden.data.vault.datasource.network.api
import androidx.annotation.Keep import androidx.annotation.Keep
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponseJson
import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.SendJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
@ -22,13 +23,15 @@ interface SendsApi {
* Create a text send. * Create a text send.
*/ */
@POST("sends") @POST("sends")
suspend fun createTextSend(@Body body: SendJsonRequest): Result<SyncResponseJson.Send> suspend fun createTextSend(@Body body: SendJsonRequest): NetworkResult<SyncResponseJson.Send>
/** /**
* Create a file send. * Create a file send.
*/ */
@POST("sends/file/v2") @POST("sends/file/v2")
suspend fun createFileSend(@Body body: SendJsonRequest): Result<CreateFileSendResponseJson> suspend fun createFileSend(
@Body body: SendJsonRequest,
): NetworkResult<CreateFileSendResponseJson>
/** /**
* Updates a send. * Updates a send.
@ -37,7 +40,7 @@ interface SendsApi {
suspend fun updateSend( suspend fun updateSend(
@Path("sendId") sendId: String, @Path("sendId") sendId: String,
@Body body: SendJsonRequest, @Body body: SendJsonRequest,
): Result<SyncResponseJson.Send> ): NetworkResult<SyncResponseJson.Send>
/** /**
* Uploads the file associated with a send. * Uploads the file associated with a send.
@ -47,23 +50,25 @@ interface SendsApi {
@Path("sendId") sendId: String, @Path("sendId") sendId: String,
@Path("fileId") fileId: String, @Path("fileId") fileId: String,
@Body body: MultipartBody, @Body body: MultipartBody,
): Result<Unit> ): NetworkResult<Unit>
/** /**
* Deletes a send. * Deletes a send.
*/ */
@DELETE("sends/{sendId}") @DELETE("sends/{sendId}")
suspend fun deleteSend(@Path("sendId") sendId: String): Result<Unit> suspend fun deleteSend(@Path("sendId") sendId: String): NetworkResult<Unit>
/** /**
* Deletes a send. * Deletes a send.
*/ */
@PUT("sends/{sendId}/remove-password") @PUT("sends/{sendId}/remove-password")
suspend fun removeSendPassword(@Path("sendId") sendId: String): Result<SyncResponseJson.Send> suspend fun removeSendPassword(
@Path("sendId") sendId: String,
): NetworkResult<SyncResponseJson.Send>
/** /**
* Gets a send. * Gets a send.
*/ */
@GET("sends/{sendId}") @GET("sends/{sendId}")
suspend fun getSend(@Path("sendId") sendId: String): Result<SyncResponseJson.Send> suspend fun getSend(@Path("sendId") sendId: String): NetworkResult<SyncResponseJson.Send>
} }

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.vault.datasource.network.api package com.x8bit.bitwarden.data.vault.datasource.network.api
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
import retrofit2.http.GET import retrofit2.http.GET
@ -13,8 +14,8 @@ interface SyncApi {
* @return A [SyncResponseJson] containing the vault response model. * @return A [SyncResponseJson] containing the vault response model.
*/ */
@GET("sync") @GET("sync")
suspend fun sync(): Result<SyncResponseJson> suspend fun sync(): NetworkResult<SyncResponseJson>
@GET("/accounts/revision-date") @GET("/accounts/revision-date")
suspend fun getAccountRevisionDateMillis(): Result<Long> suspend fun getAccountRevisionDateMillis(): NetworkResult<Long>
} }

View file

@ -4,6 +4,7 @@ import androidx.core.net.toUri
import com.bitwarden.vault.Attachment import com.bitwarden.vault.Attachment
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
import com.x8bit.bitwarden.data.platform.util.asFailure import com.x8bit.bitwarden.data.platform.util.asFailure
import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi
import com.x8bit.bitwarden.data.vault.datasource.network.api.CiphersApi import com.x8bit.bitwarden.data.vault.datasource.network.api.CiphersApi
@ -34,20 +35,26 @@ class CiphersServiceImpl(
private val clock: Clock, private val clock: Clock,
) : CiphersService { ) : CiphersService {
override suspend fun createCipher(body: CipherJsonRequest): Result<SyncResponseJson.Cipher> = override suspend fun createCipher(body: CipherJsonRequest): Result<SyncResponseJson.Cipher> =
ciphersApi.createCipher(body = body) ciphersApi
.createCipher(body = body)
.toResult()
override suspend fun createCipherInOrganization( override suspend fun createCipherInOrganization(
body: CreateCipherInOrganizationJsonRequest, body: CreateCipherInOrganizationJsonRequest,
): Result<SyncResponseJson.Cipher> = ciphersApi.createCipherInOrganization(body = body) ): Result<SyncResponseJson.Cipher> = ciphersApi
.createCipherInOrganization(body = body)
.toResult()
override suspend fun createAttachment( override suspend fun createAttachment(
cipherId: String, cipherId: String,
body: AttachmentJsonRequest, body: AttachmentJsonRequest,
): Result<AttachmentJsonResponse> = ): Result<AttachmentJsonResponse> =
ciphersApi.createAttachment( ciphersApi
.createAttachment(
cipherId = cipherId, cipherId = cipherId,
body = body, body = body,
) )
.toResult()
override suspend fun uploadAttachment( override suspend fun uploadAttachment(
attachmentJsonResponse: AttachmentJsonResponse, attachmentJsonResponse: AttachmentJsonResponse,
@ -82,6 +89,7 @@ class CiphersServiceImpl(
) )
} }
} }
.toResult()
.map { cipher } .map { cipher }
} }
@ -94,6 +102,7 @@ class CiphersServiceImpl(
cipherId = cipherId, cipherId = cipherId,
body = body, body = body,
) )
.toResult()
.map { UpdateCipherResponseJson.Success(cipher = it) } .map { UpdateCipherResponseJson.Success(cipher = it) }
.recoverCatching { throwable -> .recoverCatching { throwable ->
throwable throwable
@ -115,7 +124,8 @@ class CiphersServiceImpl(
?: return IllegalStateException("Attachment must have ID").asFailure() ?: return IllegalStateException("Attachment must have ID").asFailure()
val attachmentKey = attachment.key val attachmentKey = attachment.key
?: return IllegalStateException("Attachment must have Key").asFailure() ?: return IllegalStateException("Attachment must have Key").asFailure()
return ciphersApi.shareAttachment( return ciphersApi
.shareAttachment(
cipherId = cipherId, cipherId = cipherId,
attachmentId = attachmentId, attachmentId = attachmentId,
organizationId = organizationId, organizationId = organizationId,
@ -132,60 +142,79 @@ class CiphersServiceImpl(
) )
.build(), .build(),
) )
.toResult()
} }
override suspend fun shareCipher( override suspend fun shareCipher(
cipherId: String, cipherId: String,
body: ShareCipherJsonRequest, body: ShareCipherJsonRequest,
): Result<SyncResponseJson.Cipher> = ): Result<SyncResponseJson.Cipher> =
ciphersApi.shareCipher( ciphersApi
.shareCipher(
cipherId = cipherId, cipherId = cipherId,
body = body, body = body,
) )
.toResult()
override suspend fun updateCipherCollections( override suspend fun updateCipherCollections(
cipherId: String, cipherId: String,
body: UpdateCipherCollectionsJsonRequest, body: UpdateCipherCollectionsJsonRequest,
): Result<Unit> = ): Result<Unit> =
ciphersApi.updateCipherCollections( ciphersApi
.updateCipherCollections(
cipherId = cipherId, cipherId = cipherId,
body = body, body = body,
) )
.toResult()
override suspend fun hardDeleteCipher(cipherId: String): Result<Unit> = override suspend fun hardDeleteCipher(cipherId: String): Result<Unit> =
ciphersApi.hardDeleteCipher(cipherId = cipherId) ciphersApi
.hardDeleteCipher(cipherId = cipherId)
.toResult()
override suspend fun softDeleteCipher(cipherId: String): Result<Unit> = override suspend fun softDeleteCipher(cipherId: String): Result<Unit> =
ciphersApi.softDeleteCipher(cipherId = cipherId) ciphersApi
.softDeleteCipher(cipherId = cipherId)
.toResult()
override suspend fun deleteCipherAttachment( override suspend fun deleteCipherAttachment(
cipherId: String, cipherId: String,
attachmentId: String, attachmentId: String,
): Result<Unit> = ): Result<Unit> =
ciphersApi.deleteCipherAttachment( ciphersApi
.deleteCipherAttachment(
cipherId = cipherId, cipherId = cipherId,
attachmentId = attachmentId, attachmentId = attachmentId,
) )
.toResult()
override suspend fun restoreCipher(cipherId: String): Result<SyncResponseJson.Cipher> = override suspend fun restoreCipher(cipherId: String): Result<SyncResponseJson.Cipher> =
ciphersApi.restoreCipher(cipherId = cipherId) ciphersApi
.restoreCipher(cipherId = cipherId)
.toResult()
override suspend fun getCipher( override suspend fun getCipher(
cipherId: String, cipherId: String,
): Result<SyncResponseJson.Cipher> = ): Result<SyncResponseJson.Cipher> =
ciphersApi.getCipher(cipherId = cipherId) ciphersApi
.getCipher(cipherId = cipherId)
.toResult()
override suspend fun getCipherAttachment( override suspend fun getCipherAttachment(
cipherId: String, cipherId: String,
attachmentId: String, attachmentId: String,
): Result<SyncResponseJson.Cipher.Attachment> = ): Result<SyncResponseJson.Cipher.Attachment> =
ciphersApi.getCipherAttachment( ciphersApi
.getCipherAttachment(
cipherId = cipherId, cipherId = cipherId,
attachmentId = attachmentId, attachmentId = attachmentId,
) )
.toResult()
override suspend fun hasUnassignedCiphers(): Result<Boolean> = override suspend fun hasUnassignedCiphers(): Result<Boolean> =
ciphersApi.hasUnassignedCiphers() ciphersApi
.hasUnassignedCiphers()
.toResult()
private fun createMultipartBodyBuilder( private fun createMultipartBodyBuilder(
encryptedFile: File, encryptedFile: File,

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.vault.datasource.network.service package com.x8bit.bitwarden.data.vault.datasource.network.service
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
import com.x8bit.bitwarden.data.vault.datasource.network.api.DownloadApi import com.x8bit.bitwarden.data.vault.datasource.network.api.DownloadApi
import okhttp3.ResponseBody import okhttp3.ResponseBody
@ -12,5 +13,7 @@ class DownloadServiceImpl(
override suspend fun getDataStream( override suspend fun getDataStream(
url: String, url: String,
): Result<ResponseBody> = ): Result<ResponseBody> =
downloadApi.getDataStream(url = url) downloadApi
.getDataStream(url = url)
.toResult()
} }

View file

@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.vault.datasource.network.service
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
import com.x8bit.bitwarden.data.vault.datasource.network.api.FoldersApi import com.x8bit.bitwarden.data.vault.datasource.network.api.FoldersApi
import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest import com.x8bit.bitwarden.data.vault.datasource.network.model.FolderJsonRequest
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
@ -13,7 +14,9 @@ class FolderServiceImpl(
private val json: Json, private val json: Json,
) : FolderService { ) : FolderService {
override suspend fun createFolder(body: FolderJsonRequest): Result<SyncResponseJson.Folder> = override suspend fun createFolder(body: FolderJsonRequest): Result<SyncResponseJson.Folder> =
foldersApi.createFolder(body = body) foldersApi
.createFolder(body = body)
.toResult()
override suspend fun updateFolder( override suspend fun updateFolder(
folderId: String, folderId: String,
@ -24,6 +27,7 @@ class FolderServiceImpl(
folderId = folderId, folderId = folderId,
body = body, body = body,
) )
.toResult()
.map { UpdateFolderResponseJson.Success(folder = it) } .map { UpdateFolderResponseJson.Success(folder = it) }
.recoverCatching { throwable -> .recoverCatching { throwable ->
throwable throwable
@ -36,10 +40,13 @@ class FolderServiceImpl(
} }
override suspend fun deleteFolder(folderId: String): Result<Unit> = override suspend fun deleteFolder(folderId: String): Result<Unit> =
foldersApi.deleteFolder(folderId = folderId) foldersApi
.deleteFolder(folderId = folderId)
.toResult()
override suspend fun getFolder( override suspend fun getFolder(
folderId: String, folderId: String,
): Result<SyncResponseJson.Folder> = foldersApi ): Result<SyncResponseJson.Folder> = foldersApi
.getFolder(folderId = folderId) .getFolder(folderId = folderId)
.toResult()
} }

View file

@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.vault.datasource.network.service
import androidx.core.net.toUri import androidx.core.net.toUri
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull import com.x8bit.bitwarden.data.platform.datasource.network.util.parseErrorBodyOrNull
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi import com.x8bit.bitwarden.data.vault.datasource.network.api.AzureApi
import com.x8bit.bitwarden.data.vault.datasource.network.api.SendsApi import com.x8bit.bitwarden.data.vault.datasource.network.api.SendsApi
import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponse import com.x8bit.bitwarden.data.vault.datasource.network.model.CreateFileSendResponse
@ -34,7 +35,9 @@ class SendsServiceImpl(
override suspend fun createTextSend( override suspend fun createTextSend(
body: SendJsonRequest, body: SendJsonRequest,
): Result<CreateSendJsonResponse> = ): Result<CreateSendJsonResponse> =
sendsApi.createTextSend(body = body) sendsApi
.createTextSend(body = body)
.toResult()
.map { CreateSendJsonResponse.Success(send = it) } .map { CreateSendJsonResponse.Success(send = it) }
.recoverCatching { throwable -> .recoverCatching { throwable ->
throwable.toBitwardenError() throwable.toBitwardenError()
@ -48,7 +51,9 @@ class SendsServiceImpl(
override suspend fun createFileSend( override suspend fun createFileSend(
body: SendJsonRequest, body: SendJsonRequest,
): Result<CreateFileSendResponse> = ): Result<CreateFileSendResponse> =
sendsApi.createFileSend(body = body) sendsApi
.createFileSend(body = body)
.toResult()
.map { CreateFileSendResponse.Success(it) } .map { CreateFileSendResponse.Success(it) }
.recoverCatching { throwable -> .recoverCatching { throwable ->
throwable.toBitwardenError() throwable.toBitwardenError()
@ -68,6 +73,7 @@ class SendsServiceImpl(
sendId = sendId, sendId = sendId,
body = body, body = body,
) )
.toResult()
.map { UpdateSendResponseJson.Success(send = it) } .map { UpdateSendResponseJson.Success(send = it) }
.recoverCatching { throwable -> .recoverCatching { throwable ->
throwable throwable
@ -118,16 +124,20 @@ class SendsServiceImpl(
) )
} }
} }
.toResult()
.onFailure { sendsApi.deleteSend(send.id) } .onFailure { sendsApi.deleteSend(send.id) }
.map { send } .map { send }
} }
override suspend fun deleteSend(sendId: String): Result<Unit> = override suspend fun deleteSend(sendId: String): Result<Unit> =
sendsApi.deleteSend(sendId = sendId) sendsApi
.deleteSend(sendId = sendId)
.toResult()
override suspend fun removeSendPassword(sendId: String): Result<UpdateSendResponseJson> = override suspend fun removeSendPassword(sendId: String): Result<UpdateSendResponseJson> =
sendsApi sendsApi
.removeSendPassword(sendId = sendId) .removeSendPassword(sendId = sendId)
.toResult()
.map { UpdateSendResponseJson.Success(send = it) } .map { UpdateSendResponseJson.Success(send = it) }
.recoverCatching { throwable -> .recoverCatching { throwable ->
throwable throwable
@ -142,5 +152,7 @@ class SendsServiceImpl(
override suspend fun getSend( override suspend fun getSend(
sendId: String, sendId: String,
): Result<SyncResponseJson.Send> = ): Result<SyncResponseJson.Send> =
sendsApi.getSend(sendId = sendId) sendsApi
.getSend(sendId = sendId)
.toResult()
} }

View file

@ -1,13 +1,18 @@
package com.x8bit.bitwarden.data.vault.datasource.network.service package com.x8bit.bitwarden.data.vault.datasource.network.service
import com.x8bit.bitwarden.data.platform.datasource.network.util.toResult
import com.x8bit.bitwarden.data.vault.datasource.network.api.SyncApi import com.x8bit.bitwarden.data.vault.datasource.network.api.SyncApi
import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson
class SyncServiceImpl( class SyncServiceImpl(
private val syncApi: SyncApi, private val syncApi: SyncApi,
) : SyncService { ) : SyncService {
override suspend fun sync(): Result<SyncResponseJson> = syncApi.sync() override suspend fun sync(): Result<SyncResponseJson> = syncApi
.sync()
.toResult()
override suspend fun getAccountRevisionDateMillis(): Result<Long> = override suspend fun getAccountRevisionDateMillis(): Result<Long> =
syncApi.getAccountRevisionDateMillis() syncApi
.getAccountRevisionDateMillis()
.toResult()
} }

View file

@ -1,6 +1,6 @@
package com.x8bit.bitwarden.data.platform.base package com.x8bit.bitwarden.data.platform.base
import com.x8bit.bitwarden.data.platform.datasource.network.core.ResultCallAdapterFactory import com.x8bit.bitwarden.data.platform.datasource.network.core.NetworkResultCallAdapterFactory
import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule import com.x8bit.bitwarden.data.platform.datasource.network.di.PlatformNetworkModule
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
@ -24,7 +24,7 @@ abstract class BaseServiceTest {
protected val retrofit: Retrofit = Retrofit.Builder() protected val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(url.toString()) .baseUrl(url.toString())
.addCallAdapterFactory(ResultCallAdapterFactory()) .addCallAdapterFactory(NetworkResultCallAdapterFactory())
.addConverterFactory(json.asConverterFactory("application/json".toMediaType())) .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build() .build()

View file

@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.network.core package com.x8bit.bitwarden.data.platform.datasource.network.core
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.MockWebServer
@ -10,14 +11,14 @@ import retrofit2.Retrofit
import retrofit2.create import retrofit2.create
import retrofit2.http.GET import retrofit2.http.GET
class ResultCallAdapterTest { class NetworkResultCallAdapterTest {
private val server: MockWebServer = MockWebServer().apply { start() } private val server: MockWebServer = MockWebServer().apply { start() }
private val testService: FakeService = private val testService: FakeService =
Retrofit.Builder() Retrofit.Builder()
.baseUrl(server.url("/").toString()) .baseUrl(server.url("/").toString())
// add the adapter being tested // add the adapter being tested
.addCallAdapterFactory(ResultCallAdapterFactory()) .addCallAdapterFactory(NetworkResultCallAdapterFactory())
.build() .build()
.create() .create()
@ -30,14 +31,14 @@ class ResultCallAdapterTest {
fun `when server returns error response code result should be failure`() = runBlocking { fun `when server returns error response code result should be failure`() = runBlocking {
server.enqueue(MockResponse().setResponseCode(500)) server.enqueue(MockResponse().setResponseCode(500))
val result = testService.requestWithUnitData() val result = testService.requestWithUnitData()
assertTrue(result.isFailure) assertTrue(result is NetworkResult.Failure)
} }
@Test @Test
fun `when server returns successful response result should be success`() = runBlocking { fun `when server returns successful response result should be success`() = runBlocking {
server.enqueue(MockResponse()) server.enqueue(MockResponse())
val result = testService.requestWithUnitData() val result = testService.requestWithUnitData()
assertTrue(result.isSuccess) assertTrue(result is NetworkResult.Success)
} }
} }
@ -46,5 +47,5 @@ class ResultCallAdapterTest {
*/ */
private interface FakeService { private interface FakeService {
@GET("/fake") @GET("/fake")
suspend fun requestWithUnitData(): Result<Unit> suspend fun requestWithUnitData(): NetworkResult<Unit>
} }

View file

@ -4,6 +4,7 @@ import com.x8bit.bitwarden.data.platform.datasource.network.authenticator.Refres
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.AuthTokenInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.BaseUrlInterceptors
import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.HeadersInterceptor import com.x8bit.bitwarden.data.platform.datasource.network.interceptor.HeadersInterceptor
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.slot import io.mockk.slot
@ -39,7 +40,7 @@ class RetrofitsTest {
} }
} }
private val headersInterceptors = mockk<HeadersInterceptor> { private val headersInterceptors = mockk<HeadersInterceptor> {
mockIntercept { isheadersInterceptorCalled = true } mockIntercept { isHeadersInterceptorCalled = true }
} }
private val refreshAuthenticator = mockk<RefreshAuthenticator> { private val refreshAuthenticator = mockk<RefreshAuthenticator> {
mockAuthenticate { isRefreshAuthenticatorCalled = true } mockAuthenticate { isRefreshAuthenticatorCalled = true }
@ -57,7 +58,7 @@ class RetrofitsTest {
private var isAuthInterceptorCalled = false private var isAuthInterceptorCalled = false
private var isApiInterceptorCalled = false private var isApiInterceptorCalled = false
private var isheadersInterceptorCalled = false private var isHeadersInterceptorCalled = false
private var isIdentityInterceptorCalled = false private var isIdentityInterceptorCalled = false
private var isEventsInterceptorCalled = false private var isEventsInterceptorCalled = false
private var isRefreshAuthenticatorCalled = false private var isRefreshAuthenticatorCalled = false
@ -158,7 +159,7 @@ class RetrofitsTest {
assertTrue(isAuthInterceptorCalled) assertTrue(isAuthInterceptorCalled)
assertTrue(isApiInterceptorCalled) assertTrue(isApiInterceptorCalled)
assertTrue(isheadersInterceptorCalled) assertTrue(isHeadersInterceptorCalled)
assertFalse(isIdentityInterceptorCalled) assertFalse(isIdentityInterceptorCalled)
assertFalse(isEventsInterceptorCalled) assertFalse(isEventsInterceptorCalled)
} }
@ -176,7 +177,7 @@ class RetrofitsTest {
assertTrue(isAuthInterceptorCalled) assertTrue(isAuthInterceptorCalled)
assertFalse(isApiInterceptorCalled) assertFalse(isApiInterceptorCalled)
assertTrue(isheadersInterceptorCalled) assertTrue(isHeadersInterceptorCalled)
assertFalse(isIdentityInterceptorCalled) assertFalse(isIdentityInterceptorCalled)
assertTrue(isEventsInterceptorCalled) assertTrue(isEventsInterceptorCalled)
} }
@ -194,7 +195,7 @@ class RetrofitsTest {
assertFalse(isAuthInterceptorCalled) assertFalse(isAuthInterceptorCalled)
assertTrue(isApiInterceptorCalled) assertTrue(isApiInterceptorCalled)
assertTrue(isheadersInterceptorCalled) assertTrue(isHeadersInterceptorCalled)
assertFalse(isIdentityInterceptorCalled) assertFalse(isIdentityInterceptorCalled)
assertFalse(isEventsInterceptorCalled) assertFalse(isEventsInterceptorCalled)
} }
@ -212,7 +213,7 @@ class RetrofitsTest {
assertFalse(isAuthInterceptorCalled) assertFalse(isAuthInterceptorCalled)
assertFalse(isApiInterceptorCalled) assertFalse(isApiInterceptorCalled)
assertTrue(isheadersInterceptorCalled) assertTrue(isHeadersInterceptorCalled)
assertTrue(isIdentityInterceptorCalled) assertTrue(isIdentityInterceptorCalled)
assertFalse(isEventsInterceptorCalled) assertFalse(isEventsInterceptorCalled)
} }
@ -231,7 +232,7 @@ class RetrofitsTest {
assertTrue(isAuthInterceptorCalled) assertTrue(isAuthInterceptorCalled)
assertFalse(isApiInterceptorCalled) assertFalse(isApiInterceptorCalled)
assertTrue(isheadersInterceptorCalled) assertTrue(isHeadersInterceptorCalled)
assertFalse(isIdentityInterceptorCalled) assertFalse(isIdentityInterceptorCalled)
assertFalse(isEventsInterceptorCalled) assertFalse(isEventsInterceptorCalled)
} }
@ -250,7 +251,7 @@ class RetrofitsTest {
assertFalse(isAuthInterceptorCalled) assertFalse(isAuthInterceptorCalled)
assertFalse(isApiInterceptorCalled) assertFalse(isApiInterceptorCalled)
assertTrue(isheadersInterceptorCalled) assertTrue(isHeadersInterceptorCalled)
assertFalse(isIdentityInterceptorCalled) assertFalse(isIdentityInterceptorCalled)
assertFalse(isEventsInterceptorCalled) assertFalse(isEventsInterceptorCalled)
} }
@ -264,7 +265,7 @@ class RetrofitsTest {
interface TestApi { interface TestApi {
@GET("/test") @GET("/test")
suspend fun test(): Result<JsonObject> suspend fun test(): NetworkResult<JsonObject>
} }
/** /**

View file

@ -1,6 +1,6 @@
package com.x8bit.bitwarden.data.platform.datasource.network.util package com.x8bit.bitwarden.data.platform.datasource.network.util
import com.x8bit.bitwarden.data.platform.util.asSuccess import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import okhttp3.HttpUrl import okhttp3.HttpUrl
@ -17,53 +17,53 @@ import java.net.URL
class CallExtensionsTest { class CallExtensionsTest {
@Test @Test
fun `executeForResult returns failure when execute throws IOException`() { fun `executeForNetworkResult returns failure when execute throws IOException`() {
val request = createMockkRequest() val request = createMockkRequest()
val call = mockk<Call<Unit>> { val call = mockk<Call<Unit>> {
every { request() } returns request every { request() } returns request
every { execute() } throws IOException("Fail") every { execute() } throws IOException("Fail")
} }
val result = call.executeForResult() val result = call.executeForNetworkResult()
assertTrue(result.isFailure) assertTrue(result is NetworkResult.Failure)
} }
@Test @Test
fun `executeForResult returns failure when execute throws RuntimeException`() { fun `executeForNetworkResult returns failure when execute throws RuntimeException`() {
val request = createMockkRequest() val request = createMockkRequest()
val call = mockk<Call<Unit>> { val call = mockk<Call<Unit>> {
every { request() } returns request every { request() } returns request
every { execute() } throws RuntimeException("Fail") every { execute() } throws RuntimeException("Fail")
} }
val result = call.executeForResult() val result = call.executeForNetworkResult()
assertTrue(result.isFailure) assertTrue(result is NetworkResult.Failure)
} }
@Test @Test
fun `executeForResult returns failure when response is failure`() { fun `executeForNetworkResult returns failure when response is failure`() {
val request = createMockkRequest() val request = createMockkRequest()
val call = mockk<Call<Unit>> { val call = mockk<Call<Unit>> {
every { request() } returns request every { request() } returns request
every { execute() } returns Response.error(400, "".toResponseBody()) every { execute() } returns Response.error(400, "".toResponseBody())
} }
val result = call.executeForResult() val result = call.executeForNetworkResult()
assertTrue(result.isFailure) assertTrue(result is NetworkResult.Failure)
} }
@Test @Test
fun `executeForResult returns success when response is failure`() { fun `executeForNetworkResult returns success when response is failure`() {
val call = mockk<Call<Unit>> { val call = mockk<Call<Unit>> {
every { execute() } returns Response.success(Unit) every { execute() } returns Response.success(Unit)
} }
val result = call.executeForResult() val result = call.executeForNetworkResult()
assertEquals(Unit.asSuccess(), result) assertEquals(NetworkResult.Success(Unit), result)
} }
private fun createMockkRequest(): Request { private fun createMockkRequest(): Request {

View file

@ -0,0 +1,29 @@
package com.x8bit.bitwarden.data.platform.datasource.network.util
import com.x8bit.bitwarden.data.platform.datasource.network.model.NetworkResult
import com.x8bit.bitwarden.data.platform.util.asFailure
import com.x8bit.bitwarden.data.platform.util.asSuccess
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class NetworkResultExtensionsTest {
@Test
fun `NetworkResult toResult with success should return successful result`() {
val value = "test"
val original = NetworkResult.Success(value)
val result = original.toResult()
assertEquals(value.asSuccess(), result)
}
@Test
fun `NetworkResult toResult with failure should return failure result`() {
val exception = Exception("Failed")
val original = NetworkResult.Failure(exception)
val result = original.toResult()
assertEquals(exception.asFailure(), result)
}
}

View file

@ -311,7 +311,7 @@ class CiphersServiceTest : BaseServiceTest() {
) )
assertEquals( assertEquals(
createMockAttachment(number = 1), createMockAttachment(number = 1),
result.testGetOrThrow(), result.getOrThrow(),
) )
} }
@ -336,25 +336,6 @@ private fun setupMockUri(
return mockUri return mockUri
} }
/**
* A helper method to attempt validate that the the value is being boxed and causing test to
* inconsistently fail.
*
* This was modified from the code found here:
* * https://github.com/mockk/mockk/issues/485#issuecomment-1973170516
*/
@Suppress("INVISIBLE_REFERENCE", "UNCHECKED_CAST")
private fun <T> Result<T>.testGetOrThrow(): T =
when (val unboxed: Any? = value) {
is Result.Failure -> throw unboxed.exception
!is Result<*> -> unboxed as T
else -> {
// This means the result is boxed, we could make this recursive to address the issue.
println("Unboxed value = $unboxed")
unboxed as T
}
}
private const val CREATE_ATTACHMENT_SUCCESS_JSON = """ private const val CREATE_ATTACHMENT_SUCCESS_JSON = """
{ {
"attachmentId":"mockAttachmentId-1", "attachmentId":"mockAttachmentId-1",