Slide 14
Slide 14 text
Validated
sealed class Strategy {
object FailFast : Strategy()
object ErrorAccumulation : Strategy()
}
/** Abstracts away invoke strategy **/
object Rules {
private fun FormField.contains(needle: String): ValidatedNel =
if (value.contains(needle, false)) validNel()
else ValidationError.DoesNotContain(needle).invalidNel()
private fun FormField.maxLength(maxLength: Int): ValidatedNel =
if (value.length <= maxLength) validNel()
else ValidationError.MaxLength(maxLength).invalidNel()
private fun FormField.validateErrorAccumulate(): ValidatedNel =
contains("@").zip(
Semigroup.nonEmptyList(), // accumulates errors in a non empty list, can be omited for NonEmptyList
maxLength(250)
) { _, _ -> Email(value) }.handleErrorWith { ValidationError.NotAnEmail(it).invalidNel() }
/** either blocks support binding over Validated values with no additional cost or need to convert first to Either **/
private fun FormField.validateFailFast(): Either, Email> =
either.eager {
contains("@").bind() // fails fast on first error found
maxLength(250).bind()
Email(value)
}
operator fun invoke(strategy: Strategy, fields: List): Either, List> =
when (strategy) {
Strategy.FailFast ->
fields.traverseEither { it.validateFailFast() }
Strategy.ErrorAccumulation ->
fields.traverseValidated(Semigroup.nonEmptyList()) {
it.validateErrorAccumulate()
}.toEither()
}
}
https://arrow-kt.io/docs/patterns/error_handling/
Boilerplate