fix: Better formatting of error messages from the API
This commit is contained in:
parent
55fd6b246a
commit
553d40c45b
@ -1,15 +1,18 @@
|
||||
package com.artemchep.keyguard.common.exception
|
||||
|
||||
import com.artemchep.keyguard.feature.localization.TextHolder
|
||||
import com.artemchep.keyguard.provider.bitwarden.model.TwoFactorProviderArgument
|
||||
import io.ktor.http.HttpStatusCode
|
||||
|
||||
class ApiException(
|
||||
override val title: TextHolder,
|
||||
override val text: TextHolder?,
|
||||
val exception: Exception,
|
||||
val code: HttpStatusCode,
|
||||
val error: String,
|
||||
val type: Type?,
|
||||
message: String?,
|
||||
) : HttpException(code, message, exception) {
|
||||
) : HttpException(code, message, exception), Readable {
|
||||
sealed interface Type {
|
||||
data class CaptchaRequired(
|
||||
val siteKey: String,
|
||||
|
@ -1,12 +1,12 @@
|
||||
package com.artemchep.keyguard.common.exception
|
||||
|
||||
import com.artemchep.keyguard.feature.localization.TextHolder
|
||||
import com.artemchep.keyguard.res.Res
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
|
||||
open class OutOfMemoryKdfException(
|
||||
m: String?,
|
||||
e: Throwable?,
|
||||
) : Exception(m, e), Readable {
|
||||
override val title: StringResource
|
||||
get() = Res.strings.error_failed_generate_kdf_hash_oom
|
||||
override val title: TextHolder
|
||||
get() = TextHolder.Res(Res.strings.error_failed_generate_kdf_hash_oom)
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
package com.artemchep.keyguard.common.exception
|
||||
|
||||
import com.artemchep.keyguard.common.model.NoAnalytics
|
||||
import com.artemchep.keyguard.feature.localization.TextHolder
|
||||
import com.artemchep.keyguard.res.Res
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
|
||||
class PasswordMismatchException(
|
||||
) : RuntimeException("Invalid password"),
|
||||
Readable,
|
||||
NoAnalytics {
|
||||
override val title: StringResource
|
||||
get() = Res.strings.error_incorrect_password
|
||||
override val title: TextHolder
|
||||
get() = TextHolder.Res(Res.strings.error_incorrect_password)
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package com.artemchep.keyguard.common.exception
|
||||
|
||||
import dev.icerock.moko.resources.StringResource
|
||||
import com.artemchep.keyguard.feature.localization.TextHolder
|
||||
|
||||
interface Readable {
|
||||
val title: StringResource
|
||||
val title: TextHolder
|
||||
val text: TextHolder? get() = null
|
||||
}
|
||||
|
@ -1908,7 +1908,7 @@ private suspend fun RememberStateFlowScope.createUriItem(
|
||||
val title = data.override.name
|
||||
data.contentOrException.fold(
|
||||
ifLeft = { e ->
|
||||
val text = getErrorReadableMessage(
|
||||
val parsedMessage = getErrorReadableMessage(
|
||||
e,
|
||||
translator = this,
|
||||
)
|
||||
@ -1935,7 +1935,7 @@ private suspend fun RememberStateFlowScope.createUriItem(
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
text = parsedMessage.title,
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = LocalContentColor.current
|
||||
.combineAlpha(MediumEmphasisAlpha),
|
||||
@ -1945,7 +1945,7 @@ private suspend fun RememberStateFlowScope.createUriItem(
|
||||
}
|
||||
VaultViewItem.Uri.Override(
|
||||
title = title,
|
||||
text = text,
|
||||
text = parsedMessage.title,
|
||||
error = true,
|
||||
dropdown = dropdown,
|
||||
)
|
||||
|
@ -7,6 +7,7 @@ import com.artemchep.keyguard.common.io.attempt
|
||||
import com.artemchep.keyguard.common.io.bind
|
||||
import com.artemchep.keyguard.common.util.flow.EventFlow
|
||||
import com.artemchep.keyguard.feature.navigation.state.TranslatorScope
|
||||
import com.artemchep.keyguard.feature.navigation.state.translate
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -21,7 +22,7 @@ class LoadingTask(
|
||||
* Exception handler that's responsible for parsing the
|
||||
* error messages as user-readable messages.
|
||||
*/
|
||||
private val exceptionHandler: (Throwable) -> String = { e ->
|
||||
private val exceptionHandler: (Throwable) -> ReadableExceptionMessage = { e ->
|
||||
getErrorReadableMessage(e, translator)
|
||||
},
|
||||
) {
|
||||
@ -31,11 +32,12 @@ class LoadingTask(
|
||||
|
||||
val isExecutingFlow get() = isWorkingSink
|
||||
|
||||
val errorFlow: Flow<String> = errorSink.map { it.text }
|
||||
val errorFlow: Flow<Failure> = errorSink
|
||||
|
||||
data class Failure(
|
||||
val tag: String?,
|
||||
val text: String,
|
||||
val title: String,
|
||||
val text: String?,
|
||||
)
|
||||
|
||||
/**
|
||||
@ -54,9 +56,11 @@ class LoadingTask(
|
||||
try {
|
||||
val result = io.attempt().bind()
|
||||
if (result is Either.Left<Throwable>) {
|
||||
val parsedMessage = exceptionHandler(result.value)
|
||||
val message = Failure(
|
||||
tag = tag,
|
||||
text = exceptionHandler(result.value),
|
||||
title = parsedMessage.title,
|
||||
text = parsedMessage.text,
|
||||
)
|
||||
result.value.printStackTrace()
|
||||
errorSink.emit(message)
|
||||
@ -73,8 +77,26 @@ class LoadingTask(
|
||||
}
|
||||
}
|
||||
|
||||
data class ReadableExceptionMessage(
|
||||
val title: String,
|
||||
val text: String? = null,
|
||||
)
|
||||
|
||||
fun getErrorReadableMessage(e: Throwable, translator: TranslatorScope) =
|
||||
when (e) {
|
||||
is Readable -> translator.translate(e.title)
|
||||
else -> e.message.orEmpty()
|
||||
is Readable -> {
|
||||
val title = e.title.let(translator::translate)
|
||||
val text = e.text?.let(translator::translate)
|
||||
ReadableExceptionMessage(
|
||||
title = title,
|
||||
text = text,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val title = e.message.orEmpty()
|
||||
ReadableExceptionMessage(
|
||||
title = title,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -118,10 +118,11 @@ class RememberStateFlowScopeImpl(
|
||||
override fun message(
|
||||
exception: Throwable,
|
||||
) {
|
||||
val title = getErrorReadableMessage(exception, this)
|
||||
val parsedMessage = getErrorReadableMessage(exception, this)
|
||||
val message = ToastMessage(
|
||||
type = ToastMessage.Type.ERROR,
|
||||
title = title,
|
||||
title = parsedMessage.title,
|
||||
text = parsedMessage.text,
|
||||
)
|
||||
message(message)
|
||||
}
|
||||
@ -144,7 +145,8 @@ class RememberStateFlowScopeImpl(
|
||||
.errorFlow
|
||||
.onEach { message ->
|
||||
val model = ToastMessage(
|
||||
title = message,
|
||||
title = message.title,
|
||||
text = message.text,
|
||||
type = ToastMessage.Type.ERROR,
|
||||
)
|
||||
message(model)
|
||||
|
@ -2,6 +2,7 @@ package com.artemchep.keyguard.provider.bitwarden.entity
|
||||
|
||||
import com.artemchep.keyguard.common.exception.ApiException
|
||||
import com.artemchep.keyguard.common.service.state.impl.toMap
|
||||
import com.artemchep.keyguard.feature.localization.TextHolder
|
||||
import com.artemchep.keyguard.provider.bitwarden.model.TwoFactorProviderArgument
|
||||
import com.artemchep.keyguard.provider.bitwarden.model.TwoFactorProviderType
|
||||
import com.artemchep.keyguard.provider.bitwarden.model.toObj
|
||||
@ -196,14 +197,31 @@ fun ErrorEntity.toException(
|
||||
}
|
||||
// Auto-format the validation error
|
||||
// messages to something user-friendly.
|
||||
val validationError = validationErrors?.toMap()?.format()
|
||||
val errorTitle = kotlin.run {
|
||||
errorModel?.message?.takeIf { it.isNotBlank() }
|
||||
?: error.takeIf { it.isNotBlank() }
|
||||
// We usually should get an error message, but
|
||||
// just in case resort to the basic message.
|
||||
?: code.description
|
||||
}.trim()
|
||||
val errorText = kotlin.run {
|
||||
val validation = validationErrors?.toMap()?.format()?.takeIf { it.isNotBlank() }
|
||||
val message = listOfNotNull(
|
||||
errorDescription?.takeIf { it.isNotBlank() },
|
||||
validation,
|
||||
)
|
||||
.joinToString(separator = "\n")
|
||||
.takeIf { it.isNotBlank() }
|
||||
?.trim()
|
||||
message?.takeIf { it != errorTitle }
|
||||
}
|
||||
val message = listOfNotNull(
|
||||
errorModel?.message,
|
||||
errorDescription,
|
||||
error,
|
||||
validationError
|
||||
errorTitle,
|
||||
errorText,
|
||||
).joinToString(separator = "\n")
|
||||
ApiException(
|
||||
title = errorTitle.let(TextHolder::Value),
|
||||
text = errorText?.let(TextHolder::Value),
|
||||
exception = exception,
|
||||
code = code,
|
||||
error = error,
|
||||
@ -222,10 +240,18 @@ private fun Any?.format(): String {
|
||||
.joinToString(
|
||||
separator = "\n\n",
|
||||
) { entry ->
|
||||
"${entry.key}:\n${entry.value.format()}"
|
||||
val formattedValue = entry.value
|
||||
.format()
|
||||
if (entry.key.isBlank()) {
|
||||
formattedValue
|
||||
} else {
|
||||
"${entry.key}:\n$formattedValue"
|
||||
}
|
||||
}
|
||||
|
||||
is Collection<*> -> this
|
||||
is Collection<*> -> if (this.size == 1) {
|
||||
this.first().format()
|
||||
} else this
|
||||
.joinToString(separator = "\n") { value ->
|
||||
"- " + value.format()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user