Skip to content

Instantly share code, notes, and snippets.

@raulraja
Last active May 9, 2019 08:25
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raulraja/4b592bbbdd0c048a45ae983c9f66d2eb to your computer and use it in GitHub Desktop.
Save raulraja/4b592bbbdd0c048a45ae983c9f66d2eb to your computer and use it in GitHub Desktop.
Validation: Accumulating errors and failing fast in Arrow with `ApplicativeError`
import arrow.*
import arrow.core.*
import arrow.typeclasses.*
import arrow.data.*
sealed class ValidationError(val msg: String)
data class DoesNotContain(val value: String) : ValidationError("Did not contain $value")
data class MaxLength(val value: Int) : ValidationError("Exceeded length of $value")
data class NotAnEmail(val reasons: Nel<ValidationError>) : ValidationError("Not a valid email")
data class FormField(val label: String, val value: String)
data class Email(val value: String)
sealed class Rules<F>(A: ApplicativeError<F, Nel<ValidationError>>) : ApplicativeError<F, Nel<ValidationError>> by A {
private fun FormField.contains(needle: String): Kind<F, FormField> =
if (value.contains(needle, false)) just(this)
else raiseError(DoesNotContain(needle).nel())
private fun FormField.maxLength(maxLength: Int): Kind<F, FormField> =
if (value.length <= maxLength) just(this)
else raiseError(MaxLength(maxLength).nel())
fun FormField.validateEmail(): Kind<F, Email> =
map(contains("@"), maxLength(250), {
Email(value)
}).handleErrorWith { raiseError(NotAnEmail(it).nel()) }
object ErrorAccumulationStrategy :
Rules<ValidatedPartialOf<Nel<ValidationError>>>(Validated.applicativeError(NonEmptyList.semigroup()))
object FailFastStrategy :
Rules<EitherPartialOf<Nel<ValidationError>>>(Either.applicativeError())
companion object {
infix fun <A> failFast(f: FailFastStrategy.() -> A): A = f(FailFastStrategy)
infix fun <A> accumulateErrors(f: ErrorAccumulationStrategy.() -> A): A = f(ErrorAccumulationStrategy)
}
}
object RulesTest {
@JvmStatic
fun main(args: Array<String>): Unit {
Rules accumulateErrors {
listOf(
FormField("Invalid Email Domain Label", "nowhere.com"),
FormField("Too Long Email Label", "nowheretoolong${(0..251).map { "g" }}"),
FormField("Valid Email Label", "getlost@nowhere.com")
).forEach { println(it.validateEmail()) }
}
// Invalid(e=NonEmptyList(all=[NotAnEmail(reasons=NonEmptyList(all=[DoesNotContain(value=@)]))]))
// Invalid(e=NonEmptyList(all=[NotAnEmail(reasons=NonEmptyList(all=[DoesNotContain(value=@), MaxLength(value=250)]))]))
// Valid(a=Email(value=getlost@nowhere.com))
Rules failFast {
listOf(
FormField("Invalid Email Domain Label", "nowhere.com"),
FormField("Too Long Email Label", "nowheretoolong${(0..251).map { "g" }}"),
FormField("Valid Email Label", "getlost@nowhere.com")
).forEach { println(it.validateEmail()) }
}
// Left(a=NonEmptyList(all=[NotAnEmail(reasons=NonEmptyList(all=[DoesNotContain(value=@)]))]))
// Left(a=NonEmptyList(all=[NotAnEmail(reasons=NonEmptyList(all=[DoesNotContain(value=@)]))]))
// Right(b=Email(value=getlost@nowhere.com))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment