Created
March 5, 2020 17:51
-
-
Save raulraja/b7844cea081dab96aca95ee22201ca7c to your computer and use it in GitHub Desktop.
Validation rules abstracting strategies with operations as extension functions
This file contains 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
package arrow.rules.test | |
import arrow.Kind | |
import arrow.core.Either | |
import arrow.core.EitherPartialOf | |
import arrow.core.Nel | |
import arrow.core.NonEmptyList | |
import arrow.core.Validated | |
import arrow.core.ValidatedPartialOf | |
import arrow.core.extensions.either.applicativeError.applicativeError | |
import arrow.core.extensions.nonemptylist.semigroup.semigroup | |
import arrow.core.extensions.validated.applicativeError.applicativeError | |
import arrow.core.nel | |
import arrow.typeclasses.ApplicativeError | |
/** | |
* A generic rules class that abstracts over validation strategies | |
*/ | |
sealed class Rules<F, E>(A: ApplicativeError<F, Nel<E>>) : ApplicativeError<F, Nel<E>> by A { | |
/** | |
* Accumulates errors thanks to validated and non empty list | |
*/ | |
class ErrorAccumulationStrategy<E> : | |
Rules<ValidatedPartialOf<Nel<E>>, E>(Validated.applicativeError(NonEmptyList.semigroup())) | |
/** | |
* Fails fast thanks to Either | |
*/ | |
class FailFastStrategy<E> : | |
Rules<EitherPartialOf<Nel<E>>, E>(Either.applicativeError()) | |
/** | |
* DSL | |
*/ | |
companion object { | |
fun <E> failFast(): FailFastStrategy<E> = FailFastStrategy() | |
fun <E> accumulateErrors(): ErrorAccumulationStrategy<E> = ErrorAccumulationStrategy() | |
} | |
} | |
/** | |
* User defined model errors | |
*/ | |
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") | |
} | |
/** | |
* Arbitrary rules can be defined anywhere outside the Rules algebra | |
*/ | |
fun <F> Rules<F, ValidationError>.contains(value: String, needle: String): Kind<F, String> = | |
if (value.contains(needle, false)) just(value) | |
else raiseError(ValidationError.DoesNotContain(needle).nel()) | |
/** | |
* Arbitrary rules can be defined anywhere outside the Rules algebra | |
*/ | |
fun <F> Rules<F, ValidationError>.maxLength(value: String, maxLength: Int): Kind<F, String> = | |
if (value.length <= maxLength) just(value) | |
else raiseError(ValidationError.MaxLength(maxLength).nel()) | |
data class Email(val value: String) | |
/** | |
* Some rules that use the applicative syntax to validate and gather errors | |
*/ | |
fun <F> Rules<F, ValidationError>.validateEmail(value: String): Kind<F, Email> = | |
mapN(contains(value, "@"), maxLength(value, 250)) { | |
Email(value) | |
}.handleErrorWith { raiseError(ValidationError.NotAnEmail(it).nel()) } | |
/** | |
* Proof the same code works polymorphically | |
*/ | |
fun main() { | |
val accResult = | |
Rules.accumulateErrors<ValidationError>().run { | |
tupledN( | |
validateEmail("nowhere.com"), | |
validateEmail("nowheretoolong${(0..251).map { "g" }}") | |
) | |
} | |
val failFastResult = Rules.failFast<ValidationError>().run { | |
tupledN( | |
validateEmail("nowhere.com"), | |
validateEmail("nowheretoolong${(0..251).map { "g" }}") | |
) | |
} | |
println(accResult) | |
//Invalid(e=NonEmptyList(all=[NotAnEmail(reasons=NonEmptyList(all=[DoesNotContain(value=@)])), NotAnEmail(reasons=NonEmptyList(all=[DoesNotContain(value=@), MaxLength(value=250)]))])) | |
println(failFastResult) | |
//Left(a=NonEmptyList(all=[NotAnEmail(reasons=NonEmptyList(all=[DoesNotContain(value=@)]))])) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment