Created
October 25, 2018 21:57
-
-
Save richard-gibson/61596733a7cb65bd75139b5eabe4e36c to your computer and use it in GitHub Desktop.
Arrow Either Validation examples
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 co.brightcog.validation | |
import arrow.core.* | |
import arrow.data.* | |
import arrow.typeclasses.* | |
sealed class Failure { | |
data class EmptyString(val fieldName: String) : Failure() | |
data class InvalidCity(val fieldName: String) : Failure() | |
data class ValueOutOfRange(val i: Int) : Failure() | |
} | |
inline fun <A, B> Either<A, B>.valid(pred: (B) -> Boolean, onFail: () -> A): Either<A, B> = when (this) { | |
is Either.Left<A, B> -> this | |
is Either.Right<A, B> -> if (pred(this.b)) this else Either.Left(onFail()) | |
} | |
inline fun <A, B> Either<A, B>.toValidatedNel(): ValidatedNel<A, B> = | |
this.fold({ Invalid(Nel.of(it)) }, { Valid(it) }) | |
val zip = listOf("00111", "001122", "000333") | |
val cities = listOf("Dublin", "London", "Madrid") | |
data class Employee(val name: String, val zipCode: String, val city: String, val salary: Int) | |
fun nonBlank(fieldName: String, data: String): Either<Failure, String> = | |
data.right().valid({ it.isNotEmpty() }) { Failure.EmptyString("$fieldName cannot be blank") } | |
fun inRange(lower: Int, upper: Int, data: Int): Either<Failure, Int> = | |
data.right().valid({ it in lower..upper }) { Failure.ValueOutOfRange(data) } | |
fun validZip(data: String): Either<Failure, String> = | |
data.right().valid({ it in zip }) { Failure.InvalidCity(data) } | |
fun validCities(data: String): Either<Failure, String> = | |
data.right().valid({ it in cities }) { Failure.InvalidCity(data) } | |
fun empValidatedFromApp(name: String, zipCode: String, city: String, salary: Int): Validated<Nel<Failure>, Employee> = | |
Validated.applicative<Nel<Failure>>(NonEmptyList.semigroup()).map( | |
nonBlank("name", name).toValidatedNel(), | |
validZip(zipCode).toValidatedNel(), | |
validCities(city).toValidatedNel(), | |
inRange(10, 20, salary).toValidatedNel()) { | |
(n, z, c, s) -> Employee(n, z, c, s) | |
}.fix() | |
fun empEitherFromMonad(name: String, zipCode: String, city: String, salary: Int): Either<Failure, Employee> = | |
Either.monadError<Failure>().binding { | |
val n = nonBlank("name", name).bind() | |
val z = validZip(zipCode).bind() | |
val c = validCities(city).bind() | |
val s = inRange(10, 20, salary).bind() | |
Employee(n, z, c, s) | |
}.fix() | |
fun empEitherFromApp(name: String, zipCode: String, city: String, salary: Int): Either<Failure, Employee> = | |
Either.applicative<Failure>().map( | |
nonBlank("name", name), | |
validZip(zipCode), | |
validCities(city), | |
inRange(10, 20, salary)) { | |
(n, z, c, s) -> Employee(n, z, c, s) | |
}.fix() | |
fun main(a: Array<String>) { | |
println(empEitherFromApp("foo", "00111", "Dublin", 15)) | |
println(empEitherFromApp("", "00", "Washington", 17500)) | |
println(empEitherFromApp("foo", "00", "Washington", 17500)) | |
println() | |
println() | |
println(empEitherFromMonad("foo", "00111", "Dublin", 15)) | |
println(empEitherFromMonad("", "00", "Washington", 17500)) | |
println(empEitherFromMonad("foo", "00", "Washington", 17500)) | |
println() | |
println() | |
println(empValidatedFromApp("foo", "00111", "Dublin", 15)) | |
println(empValidatedFromApp("", "00", "Washington", 17500)) | |
println(empValidatedFromApp("foo", "00", "Washington", 17500)) | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment