From e33c4df59c655249e624846aef12e0c3eaf23d8f Mon Sep 17 00:00:00 2001 From: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com> Date: Thu, 12 Mar 2026 11:27:55 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=8D=92[PM-33394]=20debt:=20Add=20userFrie?= =?UTF-8?q?ndlyMessage=20extension=20and=20errorMessage=20to=20result=20ty?= =?UTF-8?q?pes=20(#6644)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/platform/util/ThrowableExtensions.kt | 13 ++++++++ .../repository/model/ArchiveCipherResult.kt | 8 +++-- .../repository/model/CreateFolderResult.kt | 8 +++-- .../model/DeleteAttachmentResult.kt | 8 +++-- .../repository/model/DeleteCipherResult.kt | 8 +++-- .../repository/model/DeleteFolderResult.kt | 8 +++-- .../repository/model/DeleteSendResult.kt | 8 +++-- .../model/ImportCredentialsResult.kt | 8 +++-- .../repository/model/RestoreCipherResult.kt | 8 +++-- .../repository/model/ShareCipherResult.kt | 8 +++-- .../repository/model/UnarchiveCipherResult.kt | 8 +++-- .../platform/util/ThrowableExtensionsTest.kt | 32 +++++++++++++++++++ 12 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 app/src/main/kotlin/com/x8bit/bitwarden/data/platform/util/ThrowableExtensions.kt create mode 100644 app/src/test/kotlin/com/x8bit/bitwarden/data/platform/util/ThrowableExtensionsTest.kt diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/util/ThrowableExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/util/ThrowableExtensions.kt new file mode 100644 index 0000000000..c5e381ea78 --- /dev/null +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/util/ThrowableExtensions.kt @@ -0,0 +1,13 @@ +package com.x8bit.bitwarden.data.platform.util + +import com.bitwarden.network.exception.CookieRedirectException + +/** + * Returns a user-friendly error message if this [Throwable] is an allow-listed + * exception type that carries one, or `null` otherwise. + */ +val Throwable.userFriendlyMessage: String? + get() = when (this) { + is CookieRedirectException -> message + else -> null + } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ArchiveCipherResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ArchiveCipherResult.kt index 93d4dd0065..e6e2652d7c 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ArchiveCipherResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ArchiveCipherResult.kt @@ -11,7 +11,11 @@ sealed class ArchiveCipherResult { data object Success : ArchiveCipherResult() /** - * Generic error while archiving a cipher. + * Generic error while archiving a cipher. The optional [errorMessage] may be displayed + * directly in the UI when present. */ - data class Error(val error: Throwable) : ArchiveCipherResult() + data class Error( + val error: Throwable, + val errorMessage: String? = null, + ) : ArchiveCipherResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/CreateFolderResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/CreateFolderResult.kt index 2fd3f3dabd..e5d00cfbab 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/CreateFolderResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/CreateFolderResult.kt @@ -13,7 +13,11 @@ sealed class CreateFolderResult { data class Success(val folderView: FolderView) : CreateFolderResult() /** - * Generic error while creating a folder. + * Generic error while creating a folder. The optional [errorMessage] may be displayed + * directly in the UI when present. */ - data class Error(val error: Throwable) : CreateFolderResult() + data class Error( + val error: Throwable, + val errorMessage: String? = null, + ) : CreateFolderResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteAttachmentResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteAttachmentResult.kt index b333153abc..014a9c23ac 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteAttachmentResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteAttachmentResult.kt @@ -11,7 +11,11 @@ sealed class DeleteAttachmentResult { data object Success : DeleteAttachmentResult() /** - * Generic error while deleting an attachment. + * Generic error while deleting an attachment. The optional [errorMessage] may be + * displayed directly in the UI when present. */ - data class Error(val error: Throwable) : DeleteAttachmentResult() + data class Error( + val error: Throwable, + val errorMessage: String? = null, + ) : DeleteAttachmentResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteCipherResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteCipherResult.kt index 2ad981fd27..3997d42dc9 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteCipherResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteCipherResult.kt @@ -11,7 +11,11 @@ sealed class DeleteCipherResult { data object Success : DeleteCipherResult() /** - * Generic error while deleting a cipher. + * Generic error while deleting a cipher. The optional [errorMessage] may be displayed + * directly in the UI when present. */ - data class Error(val error: Throwable) : DeleteCipherResult() + data class Error( + val error: Throwable, + val errorMessage: String? = null, + ) : DeleteCipherResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteFolderResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteFolderResult.kt index d559375ad7..6fb7bdf421 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteFolderResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteFolderResult.kt @@ -11,7 +11,11 @@ sealed class DeleteFolderResult { data object Success : DeleteFolderResult() /** - * Generic error while deleting a folder. + * Generic error while deleting a folder. The optional [errorMessage] may be displayed + * directly in the UI when present. */ - data class Error(val error: Throwable) : DeleteFolderResult() + data class Error( + val error: Throwable, + val errorMessage: String? = null, + ) : DeleteFolderResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteSendResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteSendResult.kt index c2aa01df7e..b5b32adf79 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteSendResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/DeleteSendResult.kt @@ -11,7 +11,11 @@ sealed class DeleteSendResult { data object Success : DeleteSendResult() /** - * Generic error while deleting a send. + * Generic error while deleting a send. The optional [errorMessage] may be displayed + * directly in the UI when present. */ - data class Error(val error: Throwable) : DeleteSendResult() + data class Error( + val error: Throwable, + val errorMessage: String? = null, + ) : DeleteSendResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ImportCredentialsResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ImportCredentialsResult.kt index 56d6cb9ee8..852747ad97 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ImportCredentialsResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ImportCredentialsResult.kt @@ -22,9 +22,13 @@ sealed class ImportCredentialsResult { data class SyncFailed(val error: Throwable) : ImportCredentialsResult() /** - * Indicates there was an error importing the vault data. + * Indicates there was an error importing the vault data. The optional [errorMessage] may be + * displayed directly in the UI when present. * * @param error The error that occurred during import. */ - data class Error(val error: Throwable) : ImportCredentialsResult() + data class Error( + val error: Throwable, + val errorMessage: String? = null, + ) : ImportCredentialsResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/RestoreCipherResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/RestoreCipherResult.kt index 50af6e50be..797146290d 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/RestoreCipherResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/RestoreCipherResult.kt @@ -11,7 +11,11 @@ sealed class RestoreCipherResult { data object Success : RestoreCipherResult() /** - * Generic error while restoring a cipher. + * Generic error while restoring a cipher. The optional [errorMessage] may be displayed + * directly in the UI when present. */ - data class Error(val error: Throwable) : RestoreCipherResult() + data class Error( + val error: Throwable, + val errorMessage: String? = null, + ) : RestoreCipherResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ShareCipherResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ShareCipherResult.kt index 0722fcb737..829719050d 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ShareCipherResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/ShareCipherResult.kt @@ -10,7 +10,11 @@ sealed class ShareCipherResult { data object Success : ShareCipherResult() /** - * Generic error while sharing cipher. + * Generic error while sharing cipher. The optional [errorMessage] may be displayed + * directly in the UI when present. */ - data class Error(val error: Throwable) : ShareCipherResult() + data class Error( + val error: Throwable, + val errorMessage: String? = null, + ) : ShareCipherResult() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/UnarchiveCipherResult.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/UnarchiveCipherResult.kt index 265e6574c2..900d327328 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/UnarchiveCipherResult.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/repository/model/UnarchiveCipherResult.kt @@ -11,7 +11,11 @@ sealed class UnarchiveCipherResult { data object Success : UnarchiveCipherResult() /** - * Generic error while unarchiving a cipher. + * Generic error while unarchiving a cipher. The optional [errorMessage] may be + * displayed directly in the UI when present. */ - data class Error(val error: Throwable) : UnarchiveCipherResult() + data class Error( + val error: Throwable, + val errorMessage: String? = null, + ) : UnarchiveCipherResult() } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/util/ThrowableExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/util/ThrowableExtensionsTest.kt new file mode 100644 index 0000000000..b8f983109f --- /dev/null +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/platform/util/ThrowableExtensionsTest.kt @@ -0,0 +1,32 @@ +package com.x8bit.bitwarden.data.platform.util + +import com.bitwarden.network.exception.CookieRedirectException +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Test +import java.io.IOException + +class ThrowableExtensionsTest { + + @Test + fun `userFriendlyMessage should return message for CookieRedirectException`() { + val exception = CookieRedirectException(hostname = "example.com") + assertEquals( + "Your request was interrupted because the app needed to " + + "re-authenticate. Please try again.", + exception.userFriendlyMessage, + ) + } + + @Test + fun `userFriendlyMessage should return null for IOException`() { + val exception = IOException("io error") + assertNull(exception.userFriendlyMessage) + } + + @Test + fun `userFriendlyMessage should return null for RuntimeException`() { + val exception = RuntimeException("runtime error") + assertNull(exception.userFriendlyMessage) + } +}