Change validation to use ValidationError interface for errors instead of exceptions
This commit is contained in:
parent
9bcd64f54e
commit
48ac48fda3
11 changed files with 114 additions and 39 deletions
|
@ -0,0 +1,3 @@
|
|||
package app.k9mail.core.common.domain.usecase.validation
|
||||
|
||||
interface ValidationError
|
|
@ -1,7 +1,7 @@
|
|||
package app.k9mail.core.common.domain.usecase.validation
|
||||
|
||||
interface ValidationResult {
|
||||
sealed interface ValidationResult {
|
||||
object Success : ValidationResult
|
||||
|
||||
data class Failure(val error: Exception) : ValidationResult
|
||||
data class Failure(val error: ValidationError) : ValidationResult
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package app.k9mail.feature.account.setup.domain.input
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationError
|
||||
|
||||
/**
|
||||
* InputField is an interface defining the state of an input field.
|
||||
*
|
||||
|
@ -7,7 +9,7 @@ package app.k9mail.feature.account.setup.domain.input
|
|||
*/
|
||||
interface InputField<T> {
|
||||
val value: T
|
||||
val errorMessage: String?
|
||||
val error: ValidationError?
|
||||
val isValid: Boolean
|
||||
|
||||
/**
|
||||
|
@ -19,11 +21,11 @@ interface InputField<T> {
|
|||
fun updateValue(value: T): InputField<T>
|
||||
|
||||
/**
|
||||
* Updates the current error message of the input field.
|
||||
* Updates the current error of the input field.
|
||||
*
|
||||
* @param errorMessage The new error message to be set for the input field.
|
||||
* @param error The new error to be set for the input field.
|
||||
*/
|
||||
fun updateErrorMessage(errorMessage: String?): InputField<T>
|
||||
fun updateError(error: ValidationError?): InputField<T>
|
||||
|
||||
/**
|
||||
* Updates the current validity of the input field.
|
||||
|
@ -38,6 +40,6 @@ interface InputField<T> {
|
|||
* @return a Boolean indicating whether the input field has an error.
|
||||
*/
|
||||
fun hasError(): Boolean {
|
||||
return errorMessage != null
|
||||
return error != null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
package app.k9mail.feature.account.setup.domain.input
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationError
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
|
||||
data class StringInputField(
|
||||
override val value: String = "",
|
||||
override val errorMessage: String? = null,
|
||||
override val error: ValidationError? = null,
|
||||
override val isValid: Boolean = false,
|
||||
) : InputField<String> {
|
||||
|
||||
override fun updateValue(value: String): StringInputField {
|
||||
return StringInputField(
|
||||
value = value,
|
||||
errorMessage = null,
|
||||
error = null,
|
||||
isValid = false,
|
||||
)
|
||||
}
|
||||
|
||||
override fun updateErrorMessage(errorMessage: String?): StringInputField {
|
||||
override fun updateError(error: ValidationError?): StringInputField {
|
||||
return StringInputField(
|
||||
value = value,
|
||||
errorMessage = errorMessage,
|
||||
error = error,
|
||||
isValid = false,
|
||||
)
|
||||
}
|
||||
|
@ -27,8 +30,22 @@ data class StringInputField(
|
|||
|
||||
return StringInputField(
|
||||
value = value,
|
||||
errorMessage = null,
|
||||
error = null,
|
||||
isValid = isValid,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun StringInputField.fromValidationResult(result: ValidationResult): StringInputField {
|
||||
return when (result) {
|
||||
is ValidationResult.Success -> copy(
|
||||
error = null,
|
||||
isValid = true,
|
||||
)
|
||||
|
||||
is ValidationResult.Failure -> copy(
|
||||
error = result.error,
|
||||
isValid = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
package app.k9mail.feature.account.setup.domain.usecase
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationError
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationUseCase
|
||||
|
||||
class ValidateAccountName : ValidationUseCase<String> {
|
||||
override fun execute(input: String): ValidationResult {
|
||||
return when {
|
||||
input.isBlank() -> ValidationResult.Failure(EmptyAccountName())
|
||||
input.isEmpty() -> ValidationResult.Success
|
||||
input.isBlank() -> ValidationResult.Failure(ValidateAccountNameError.BlankAccountName)
|
||||
else -> ValidationResult.Success
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyAccountName : Exception()
|
||||
sealed interface ValidateAccountNameError : ValidationError {
|
||||
object BlankAccountName : ValidateAccountNameError
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package app.k9mail.feature.account.setup.domain.usecase
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationError
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationUseCase
|
||||
|
||||
|
@ -7,10 +8,12 @@ class ValidateDisplayName : ValidationUseCase<String> {
|
|||
|
||||
override fun execute(input: String): ValidationResult {
|
||||
return when {
|
||||
input.isBlank() -> ValidationResult.Failure(EmptyDisplayName())
|
||||
input.isBlank() -> ValidationResult.Failure(ValidateDisplayNameError.EmptyDisplayName)
|
||||
else -> ValidationResult.Success
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyDisplayName : Exception()
|
||||
sealed interface ValidateDisplayNameError : ValidationError {
|
||||
object EmptyDisplayName : ValidateDisplayNameError
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
package app.k9mail.feature.account.setup.domain.usecase
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationError
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationUseCase
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateEmailSignature.ValidateEmailSignatureError.BlankEmailSignature
|
||||
|
||||
// TODO check signature for input validity
|
||||
class ValidateEmailSignature : ValidationUseCase<String> {
|
||||
|
||||
override fun execute(input: String): ValidationResult {
|
||||
return when {
|
||||
input.isBlank() -> ValidationResult.Failure(EmptyEmailSignature())
|
||||
input.isEmpty() -> ValidationResult.Success
|
||||
input.isBlank() -> ValidationResult.Failure(error = BlankEmailSignature)
|
||||
else -> ValidationResult.Success
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyEmailSignature : Exception()
|
||||
sealed interface ValidateEmailSignatureError : ValidationError {
|
||||
object BlankEmailSignature : ValidateEmailSignatureError
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package app.k9mail.feature.account.setup.domain.input
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationError
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
import assertk.all
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
|
@ -17,20 +19,23 @@ class StringInputFieldTest {
|
|||
|
||||
assertThat(stringInputState).all {
|
||||
prop(StringInputField::value).isEqualTo("")
|
||||
prop(StringInputField::errorMessage).isNull()
|
||||
prop(StringInputField::error).isNull()
|
||||
prop(StringInputField::isValid).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should reset errorMessage and isValid when value changed`() {
|
||||
val initialInputState = StringInputField(errorMessage = "error", isValid = false)
|
||||
val initialInputState = StringInputField(
|
||||
error = TestValidationError,
|
||||
isValid = false,
|
||||
)
|
||||
|
||||
val result = initialInputState.updateValue("new value")
|
||||
|
||||
assertThat(result).all {
|
||||
prop(StringInputField::value).isEqualTo("new value")
|
||||
prop(StringInputField::errorMessage).isNull()
|
||||
prop(StringInputField::error).isNull()
|
||||
prop(StringInputField::isValid).isFalse()
|
||||
}
|
||||
}
|
||||
|
@ -39,24 +44,27 @@ class StringInputFieldTest {
|
|||
fun `should reset isValid when error set`() {
|
||||
val initialInputState = StringInputField(value = "input", isValid = true)
|
||||
|
||||
val result = initialInputState.updateErrorMessage("error")
|
||||
val result = initialInputState.updateError(TestValidationError)
|
||||
|
||||
assertThat(result).all {
|
||||
prop(StringInputField::value).isEqualTo("input")
|
||||
prop(StringInputField::errorMessage).isEqualTo("error")
|
||||
prop(StringInputField::error).isEqualTo(TestValidationError)
|
||||
prop(StringInputField::isValid).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should reset errorMessage when valid`() {
|
||||
val initialInputState = StringInputField(value = "input", errorMessage = "error")
|
||||
val initialInputState = StringInputField(
|
||||
value = "input",
|
||||
error = TestValidationError,
|
||||
)
|
||||
|
||||
val result = initialInputState.updateValidity(isValid = true)
|
||||
|
||||
assertThat(result).all {
|
||||
prop(StringInputField::value).isEqualTo("input")
|
||||
prop(StringInputField::errorMessage).isNull()
|
||||
prop(StringInputField::error).isNull()
|
||||
prop(StringInputField::isValid).isTrue()
|
||||
}
|
||||
}
|
||||
|
@ -65,15 +73,49 @@ class StringInputFieldTest {
|
|||
fun `should not reset errorMessage when invalid`() {
|
||||
val initialInputState = StringInputField(
|
||||
value = "input",
|
||||
errorMessage = "error",
|
||||
error = TestValidationError,
|
||||
)
|
||||
|
||||
val result = initialInputState.updateValidity(isValid = false)
|
||||
|
||||
assertThat(result).all {
|
||||
prop(StringInputField::value).isEqualTo("input")
|
||||
prop(StringInputField::errorMessage).isEqualTo("error")
|
||||
prop(StringInputField::error).isEqualTo(TestValidationError)
|
||||
prop(StringInputField::isValid).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should map from success ValidationResult`() {
|
||||
val initialInputState = StringInputField(
|
||||
value = "input",
|
||||
error = TestValidationError,
|
||||
)
|
||||
|
||||
val result = initialInputState.fromValidationResult(ValidationResult.Success)
|
||||
|
||||
assertThat(result).all {
|
||||
prop(StringInputField::value).isEqualTo("input")
|
||||
prop(StringInputField::error).isNull()
|
||||
prop(StringInputField::isValid).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should map from failure ValidationResult`() {
|
||||
val initialInputState = StringInputField(
|
||||
value = "input",
|
||||
error = null,
|
||||
)
|
||||
|
||||
val result = initialInputState.fromValidationResult(ValidationResult.Failure(TestValidationError))
|
||||
|
||||
assertThat(result).all {
|
||||
prop(StringInputField::value).isEqualTo("input")
|
||||
prop(StringInputField::error).isEqualTo(TestValidationError)
|
||||
prop(StringInputField::isValid).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
private object TestValidationError : ValidationError
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package app.k9mail.feature.account.setup.domain.usecase
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateAccountName.ValidateAccountNameError
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isInstanceOf
|
||||
import assertk.assertions.prop
|
||||
|
@ -18,14 +19,12 @@ class ValidateAccountNameTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should fail when account name is empty`() {
|
||||
fun `should succeed when account name is empty`() {
|
||||
val useCase = ValidateAccountName()
|
||||
|
||||
val result = useCase.execute("")
|
||||
|
||||
assertThat(result).isInstanceOf(ValidationResult.Failure::class)
|
||||
.prop(ValidationResult.Failure::error)
|
||||
.isInstanceOf(ValidateAccountName.EmptyAccountName::class)
|
||||
assertThat(result).isInstanceOf(ValidationResult.Success::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -36,6 +35,6 @@ class ValidateAccountNameTest {
|
|||
|
||||
assertThat(result).isInstanceOf(ValidationResult.Failure::class)
|
||||
.prop(ValidationResult.Failure::error)
|
||||
.isInstanceOf(ValidateAccountName.EmptyAccountName::class)
|
||||
.isInstanceOf(ValidateAccountNameError.BlankAccountName::class)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package app.k9mail.feature.account.setup.domain.usecase
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateDisplayName.ValidateDisplayNameError
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isInstanceOf
|
||||
import assertk.assertions.prop
|
||||
|
@ -25,7 +26,7 @@ class ValidateDisplayNameTest {
|
|||
|
||||
assertThat(result).isInstanceOf(ValidationResult.Failure::class)
|
||||
.prop(ValidationResult.Failure::error)
|
||||
.isInstanceOf(ValidateDisplayName.EmptyDisplayName::class)
|
||||
.isInstanceOf(ValidateDisplayNameError.EmptyDisplayName::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -36,6 +37,6 @@ class ValidateDisplayNameTest {
|
|||
|
||||
assertThat(result).isInstanceOf(ValidationResult.Failure::class)
|
||||
.prop(ValidationResult.Failure::error)
|
||||
.isInstanceOf(ValidateDisplayName.EmptyDisplayName::class)
|
||||
.isInstanceOf(ValidateDisplayNameError.EmptyDisplayName::class)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package app.k9mail.feature.account.setup.domain.usecase
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateEmailSignature.ValidateEmailSignatureError
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isInstanceOf
|
||||
import assertk.assertions.prop
|
||||
|
@ -18,14 +19,12 @@ class ValidateEmailSignatureTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should fail when email signature is empty`() {
|
||||
fun `should succeed when email signature is empty`() {
|
||||
val useCase = ValidateEmailSignature()
|
||||
|
||||
val result = useCase.execute("")
|
||||
|
||||
assertThat(result).isInstanceOf(ValidationResult.Failure::class)
|
||||
.prop(ValidationResult.Failure::error)
|
||||
.isInstanceOf(ValidateEmailSignature.EmptyEmailSignature::class)
|
||||
assertThat(result).isInstanceOf(ValidationResult.Success::class)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -36,6 +35,6 @@ class ValidateEmailSignatureTest {
|
|||
|
||||
assertThat(result).isInstanceOf(ValidationResult.Failure::class)
|
||||
.prop(ValidationResult.Failure::error)
|
||||
.isInstanceOf(ValidateEmailSignature.EmptyEmailSignature::class)
|
||||
.isInstanceOf(ValidateEmailSignatureError.BlankEmailSignature::class)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue