Created
August 16, 2017 16:21
-
-
Save loicknuchel/e38ab17ec3ce1f777b766ddc7fad8605 to your computer and use it in GitHub Desktop.
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 scala.util.{Failure, Success, Try} | |
object ValidationMonad { | |
sealed abstract class Validation[+A] { | |
def isValid: Boolean | |
def get: A | |
def getOrElse[B >: A](default: => B): B | |
def map[B](f: A => B): Validation[B] | |
def flatMap[B](f: A => Validation[B]): Validation[B] | |
} | |
case class Valid[A](value: A) extends Validation[A] { | |
def isValid: Boolean = true | |
def get: A = value | |
def getOrElse[B >: A](default: => B): B = value | |
def map[B](f: A => B): Validation[B] = Valid(f(value)) | |
def flatMap[B](f: A => Validation[B]): Validation[B] = f(value) | |
} | |
case class Invalid[A](errors: Seq[String]) extends Validation[A] { | |
def isValid: Boolean = false | |
def get: A = throw new NoSuchElementException("Invalid.get") | |
def getOrElse[B >: A](default: => B): B = default | |
def map[B](f: A => B): Validation[B] = Invalid(errors) | |
def flatMap[B](f: A => Validation[B]): Validation[B] = f(null.asInstanceOf[A]) match { | |
case Valid(_) => Invalid(errors) | |
case Invalid(errs) => Invalid(errors ++ errs) | |
} | |
} | |
object Validation { | |
def apply[A](value: => A, error: Throwable => String = _.getMessage): Validation[A] = | |
Try(value) match { | |
case Success(v) => Valid(v) | |
case Failure(e) => e match { | |
case _: NullPointerException => Invalid() | |
case _ => Invalid(error(e)) | |
} | |
} | |
def apply[A](value: => A, message: String): Validation[A] = | |
apply(value, _ => message) | |
} | |
object Invalid { | |
def apply[A](errors: String*): Validation[A] = | |
Invalid(errors) | |
} | |
} |
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 org.scalatest.{FunSpec, Matchers} | |
class ValidationMonadSpec extends FunSpec with Matchers { | |
describe("ValidationMonad") { | |
import ValidationMonad._ | |
it("should tell if valid") { | |
Valid(1).isValid shouldBe true | |
Invalid().isValid shouldBe false | |
} | |
it("should extract the value") { | |
Valid(1).get shouldBe 1 | |
assertThrows[NoSuchElementException] { | |
Invalid().get | |
} | |
} | |
it("should extract the value with default") { | |
Valid(1).getOrElse(2) shouldBe 1 | |
Invalid().getOrElse(2) shouldBe 2 | |
} | |
it("should transform the value") { | |
Valid(1).map(_.toString) shouldBe Valid("1") | |
Invalid[Int]().map(_.toString) shouldBe Invalid() | |
} | |
it("should flatMap the value") { | |
Valid(1).flatMap(n => Valid(n + 1)) shouldBe Valid(2) | |
Valid(1).flatMap(_ => Invalid()) shouldBe Invalid() | |
Invalid[Int]().flatMap(n => Valid(n + 1)) shouldBe Invalid() | |
Invalid[Int]().flatMap(_ => Invalid()) shouldBe Invalid() | |
} | |
it("should succeed when all validations are valid") { | |
val map = Map( | |
"a" -> 1, | |
"b" -> 2, | |
"c" -> 3, | |
"d" -> 4) | |
val result: Validation[Int] = for { | |
a <- Validation(map("a"), "key 'a' is missing") | |
b <- Validation(map("b"), "key 'b' is missing") | |
c <- Validation(map("c"), "key 'c' is missing") | |
d <- Validation(map("d"), "key 'd' is missing") | |
} yield a + b + c + d | |
result shouldBe Valid(10) | |
} | |
it("should return the error if exists") { | |
val map = Map( | |
"a" -> 1, | |
"b" -> 2, | |
"d" -> 4) | |
val result: Validation[Int] = for { | |
a <- Validation(map("a"), "key 'a' is missing") | |
b <- Validation(map("b"), "key 'b' is missing") | |
c <- Validation(map("c"), "key 'c' is missing") | |
d <- Validation(map("d"), "key 'd' is missing") | |
} yield a + b + c + d | |
result shouldBe Invalid("key 'c' is missing") | |
} | |
it("should return all errors") { | |
val map = Map( | |
"a" -> 1, | |
"d" -> 4) | |
val result: Validation[Int] = for { | |
a <- Validation(map("a"), "key 'a' is missing") | |
b <- Validation(map("b"), "key 'b' is missing") | |
c <- Validation(map("c"), "key 'c' is missing") | |
d <- Validation(map("d"), "key 'd' is missing") | |
} yield a + b + c + d | |
result shouldBe Invalid("key 'b' is missing", "key 'c' is missing") | |
} | |
it("should not return null errors") { | |
val map = Map( | |
"a" -> "a", | |
"c" -> "c", | |
"d" -> "d") | |
val result: Validation[String] = for { | |
a <- Validation(map("a"), "key 'a' is missing") | |
b <- Validation(map("b"), "key 'b' is missing") | |
c <- Validation(map(b.toString), "key 'c' is missing") | |
d <- Validation(map("d"), "key 'd' is missing") | |
} yield a + b + c + d | |
result shouldBe Invalid("key 'b' is missing") | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment