Last active
May 9, 2019 08:25
-
-
Save raulraja/4b592bbbdd0c048a45ae983c9f66d2eb to your computer and use it in GitHub Desktop.
Validation: Accumulating errors and failing fast in Arrow with `ApplicativeError`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", "[email protected]") | |
).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([email protected])) | |
Rules failFast { | |
listOf( | |
FormField("Invalid Email Domain Label", "nowhere.com"), | |
FormField("Too Long Email Label", "nowheretoolong${(0..251).map { "g" }}"), | |
FormField("Valid Email Label", "[email protected]") | |
).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([email protected])) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment