Código que debe compilar con scalaz 7.1.3 y con scala 2.11.5
sealed abstract class Validation[E, A] {
...
}
case class Success[A](a: A) extends Validation[Nothing, A]
case class Failure[E](e: E) extends Validation[E, Nothing]
La idea es manipular cosas del tipo Validation sin saber si son Success's o Failure's
sealed abstract class Validation[E, A] {
...
}
case class Success[A](a: A) extends Validation[Nothing, A]
case class Failure[E](e: E) extends Validation[E, Nothing]
La idea es manipular cosas del tipo Validation sin saber si son Success's o Failure's
Por debajo si es un Success se continua la computación o si es un Failure se detiene
import scalaz.Validation
def escalar(d: Double): Validation[String, Int] = {
if(d >= 0 && d <= 1)
Validation.success((d*100).toInt)
else
Validation.failure(s"Numero debe estar entre 0 y 1 ($d)")
}
def validarProbabilidad(p: Int) = {
if(p >= 0 && p <= 100)
Validation.success(p)
else
Validation.failure(s"$p no es una probabilidad")
}
def sumarPorcentajes(n1: Int, n2: Int): Validation[String, Int] = {
validarProbabilidad(n1 + n2)
}
def computo(d1: Double, d2: Double): Validation[String, Double] = {
for {
n1 <- escalar(d1)
n2 <- escalar(d2)
r <- sumarPorcentajes(n1,n2)
} yield r / 100.0
}
map y flatMap son "fail-fast". Es decir si se llaman con un objeto que es un Failure, el resultado es ése Failure. Si el objeto es un Success se llama la función pasándole el valor del Success
scala> computo(0.25, 0.5)
res7: scalaz.Validation[String,Double] = Success(0.75)
scala> computo(0.75, 0.25)
res8: scalaz.Validation[String,Double] = Success(1.0)
scala> computo(1.1, 0.0)
res9: scalaz.Validation[String,Double] = Failure(Numero debe estar entre 0 y 1 (1.1))
scala> computo(0.75, 0.26)
res12: scalaz.Validation[String,Double] = Failure(101 no es una probabilidad)
scala> computo(0.0, 1.2)
res10: scalaz.Validation[String,Double] = Failure(Numero debe estar entre 0 y 1 (1.2))
scala> computo(1.1, 1.2)
res11: scalaz.Validation[String,Double] = Failure(Numero debe estar entre 0 y 1 (1.1))
type ValidationNel[E, X] = Validation[NonEmptyList[E], X]
ValidationNEL es un Validation cuyo tipo a la izquierda es una lista no vacía de errores
type ValidationNel[E, X] = Validation[NonEmptyList[E], X]
ValidationNEL es un Validation cuyo tipo a la izquierda es una lista no vacía de errores
Sirve para acumular errores
type ValidationNel[E, X] = Validation[NonEmptyList[E], X]
ValidationNEL es un Validation cuyo tipo a la izquierda es una lista no vacía de errores
Sirve para acumular errores
Puede ser útil para reportar más de un error a la vez y no solamente el primer error que se produce
type ValidationNel[E, X] = Validation[NonEmptyList[E], X]
ValidationNEL es un Validation cuyo tipo a la izquierda es una lista no vacía de errores
Sirve para acumular errores
Puede ser útil para reportar más de un error a la vez y no solamente el primer error que se produce
import scalaz.Validation
...
def computo(d1: Double, d2: Double): Validation[String, Double] = {
for {
n1 <- escalar(d1)
n2 <- escalar(d2)
r <- sumarPorcentajes(n1,n2)
} yield r / 100.0
}
type ValidationNel[E, X] = Validation[NonEmptyList[E], X]
ValidationNEL es un Validation cuyo tipo a la izquierda es una lista no vacía de errores
Sirve para acumular errores
Puede ser útil para reportar más de un error a la vez y no solamente el primer error que se produce
import scalaz.Validation
...
def computo(d1: Double, d2: Double): Validation[String, Double] = {
for {
n1 <- escalar(d1)
n2 <- escalar(d2)
r <- sumarPorcentajes(n1,n2)
} yield r / 100.0
}
escalar(d1) y escalar(d2) podrían fallar independientemente
import scalaz.Validation
...
def computo(d1: Double, d2: Double): Validation[String, Double] = {
for {
n1 <- escalar(d1)
n2 <- escalar(d2)
r <- sumarPorcentajes(n1,n2)
} yield r / 100.0
}
escalar(d1) y escalar(d2) podrían fallar independientemente
Con ValidationNel podríamos reportar que escalar(d1) y escalar(d2) fallaron
import scalaz.Validation
...
def computo(d1: Double, d2: Double): Validation[String, Double] = {
for {
n1 <- escalar(d1)
n2 <- escalar(d2)
r <- sumarPorcentajes(n1,n2)
} yield r / 100.0
}
escalar(d1) y escalar(d2) podrían fallar independientemente
Con ValidationNel podríamos reportar que escalar(d1) y escalar(d2) fallaron
En vez de utilizar flatMap (que es "fail-fast") podemos usar |@| (que acumula errores)
import scalaz.ValidationNel
import scalaz.syntax.applicative._
def computo(d1: Double, d2: Double): ValidationNel[String, Double] = {
(escalar(d1).toValidationNel |@| escalar(d2).toValidationNel) { (d1,d2) =>
d1 + d2
} flatMap { s =>
validarProbabilidad(s).toValidationNel
} map { s =>
s / 100.0
}
}
import scalaz.ValidationNel
import scalaz.syntax.applicative._
def computo(d1: Double, d2: Double): ValidationNel[String, Double] = {
(escalar(d1).toValidationNel |@| escalar(d2).toValidationNel) { (d1,d2) =>
d1 + d2
} flatMap { s =>
validarProbabilidad(s).toValidationNel
} map { s =>
s / 100.0
}
}
def computo(d1: Double, d2: Double): ValidationNel[String, Double] = {
val sumaVal = (escalar(d1).toValidationNel |@| escalar(d2).toValidationNel) { (d1,d2) =>
d1 + d2
}
for {
suma <- sumaVal
s <- validarProbabilidad(suma).toValidationNel
} yield (s / 100.0)
}
Utilizando for-comprehension:
import scalaz.ValidationNel
import scalaz.syntax.applicative._
def computo(d1: Double, d2: Double): ValidationNel[String, Double] = {
(escalar(d1).toValidationNel |@| escalar(d2).toValidationNel) { (d1,d2) =>
d1 + d2
} flatMap { s =>
validarProbabilidad(s).toValidationNel
} map { s =>
s / 100.0
}
}
def computo(d1: Double, d2: Double): ValidationNel[String, Double] = {
val sumaVal = (escalar(d1).toValidationNel |@| escalar(d2).toValidationNel) { (d1,d2) =>
d1 + d2
}
for {
suma <- sumaVal
s <- validarProbabilidad(suma).toValidationNel
} yield (s / 100.0)
}
Utilizando for-comprehension:
Para poder utilizar la sintáxis de |@| toca hacer este import
scala> computo(0.25, 0.5)
res18: scalaz.ValidationNel[String,Double] = Success(0.75)
scala> computo(0.75, 0.25)
res19: scalaz.ValidationNel[String,Double] = Success(1.0)
scala> computo(1.1, 0.0)
res20: scalaz.ValidationNel[String,Double] = Failure(NonEmptyList(Numero debe estar entre 0 y 1 (1.1)))
scala> computo(0.75, 0.26)
res21: scalaz.ValidationNel[String,Double] = Failure(NonEmptyList(101 no es una probabilidad))
scala> computo(0.0, 1.2)
res22: scalaz.ValidationNel[String,Double] = Failure(NonEmptyList(Numero debe estar entre 0 y 1 (1.2)))
scala> computo(1.1, 1.2)
res23: scalaz.ValidationNel[String,Double] = Failure(NonEmptyList(Numero debe estar entre 0 y 1 (1.1), Numero debe estar entre 0 y 1 (1.2)))
case class InvalidParameterValue(msg: String) extends WesuranceException(msg)
case class MultipleInvalidParameters(msg: String, errors: NonEmptyList[InvalidParameterValue]) extends WesuranceException(msg)
case class InvalidParameterValue(msg: String) extends WesuranceException(msg)
case class MultipleInvalidParameters(msg: String, errors: NonEmptyList[InvalidParameterValue]) extends WesuranceException(msg)
package co.com.wesurance.commons
import scalaz.{Validation, ValidationNel}
package object exceptions {
type WesuranceValidationNEL[A] = ValidationNel[InvalidParameterValue, A]
/**
* Constructores
*/
object WesuranceValidationNEL {
def success[A](a: A) = Validation.success(a)
def failure(msg: String) = Validation.failureNel(InvalidParameterValue(msg))
def getOrThrowException[A](validation: WesuranceValidationNEL[A], msg: String): A = {
validation.valueOr { errors =>
if(errors.size == 1)
throw errors.head
else
throw MultipleInvalidParameters(msg, errors)
}
}
}
}
case class InvalidParameterValue(msg: String) extends WesuranceException(msg)
case class MultipleInvalidParameters(msg: String, errors: NonEmptyList[InvalidParameterValue]) extends WesuranceException(msg)
package co.com.wesurance.commons
import scalaz.{Validation, ValidationNel}
package object exceptions {
type WesuranceValidationNEL[A] = ValidationNel[InvalidParameterValue, A]
/**
* Constructores
*/
object WesuranceValidationNEL {
def success[A](a: A) = Validation.success(a)
def failure(msg: String) = Validation.failureNel(InvalidParameterValue(msg))
def getOrThrowException[A](validation: WesuranceValidationNEL[A], msg: String): A = {
validation.valueOr { errors =>
if(errors.size == 1)
throw errors.head
else
throw MultipleInvalidParameters(msg, errors)
}
}
}
}
Type alias con nuestro tipo de error a la izquierda
case class InvalidParameterValue(msg: String) extends WesuranceException(msg)
case class MultipleInvalidParameters(msg: String, errors: NonEmptyList[InvalidParameterValue]) extends WesuranceException(msg)
package co.com.wesurance.commons
import scalaz.{Validation, ValidationNel}
package object exceptions {
type WesuranceValidationNEL[A] = ValidationNel[InvalidParameterValue, A]
/**
* Constructores
*/
object WesuranceValidationNEL {
def success[A](a: A) = Validation.success(a)
def failure(msg: String) = Validation.failureNel(InvalidParameterValue(msg))
def getOrThrowException[A](validation: WesuranceValidationNEL[A], msg: String): A = {
validation.valueOr { errors =>
if(errors.size == 1)
throw errors.head
else
throw MultipleInvalidParameters(msg, errors)
}
}
}
}
Type alias con nuestro tipo de error a la izquierda
Constructores utilitarios
case class InvalidParameterValue(msg: String) extends WesuranceException(msg)
case class MultipleInvalidParameters(msg: String, errors: NonEmptyList[InvalidParameterValue]) extends WesuranceException(msg)
package co.com.wesurance.commons
import scalaz.{Validation, ValidationNel}
package object exceptions {
type WesuranceValidationNEL[A] = ValidationNel[InvalidParameterValue, A]
/**
* Constructores
*/
object WesuranceValidationNEL {
def success[A](a: A) = Validation.success(a)
def failure(msg: String) = Validation.failureNel(InvalidParameterValue(msg))
def getOrThrowException[A](validation: WesuranceValidationNEL[A], msg: String): A = {
validation.valueOr { errors =>
if(errors.size == 1)
throw errors.head
else
throw MultipleInvalidParameters(msg, errors)
}
}
}
}
Type alias con nuestro tipo de error a la izquierda
Constructores utilitarios
Para extraer el valor o lanzar una excepción
def validateTextWithoutHTMLelements(text: String): WesuranceValidationNEL[String] = {
if(text.matches(htmlElemRegex))
WesuranceValidationNEL.failure(s"The text can't contain HTML elements")
else
WesuranceValidationNEL.success(text)
}
def validatePlainTextWithoutUrls(text: String): WesuranceValidationNEL[String] = {
if(!text.matches(urlRegex))
WesuranceValidationNEL.success(text)
else
WesuranceValidationNEL.failure("The text can't contain URLs")
}
A partir de validaciones pequeñas:
def validateTextWithoutHTMLelements(text: String): WesuranceValidationNEL[String] = {
if(text.matches(htmlElemRegex))
WesuranceValidationNEL.failure(s"The text can't contain HTML elements")
else
WesuranceValidationNEL.success(text)
}
def validatePlainTextWithoutUrls(text: String): WesuranceValidationNEL[String] = {
if(!text.matches(urlRegex))
WesuranceValidationNEL.success(text)
else
WesuranceValidationNEL.failure("The text can't contain URLs")
}
def validateCommunityName(name: String): WesuranceValidationNEL[String] = {
val v1 = TextValidation.validatePlainTextWithoutUrls(name)
val v2 = TextValidation.validateTextWithoutHTMLelements(name)
(v1 |@| v2) { case _ => name } leftMap { errors =>
errors.map(error => InvalidParameterValue(s"The community's name is invalid: ${error.msg}") )
}
}
A partir de validaciones pequeñas:
Vamos construyendo validaciones mas grandes:
En caso de que ambas validaciones sean exitosas devolvemos un Success(name)
def validateTextWithoutHTMLelements(text: String): WesuranceValidationNEL[String] = {
if(text.matches(htmlElemRegex))
WesuranceValidationNEL.failure(s"The text can't contain HTML elements")
else
WesuranceValidationNEL.success(text)
}
def validatePlainTextWithoutUrls(text: String): WesuranceValidationNEL[String] = {
if(!text.matches(urlRegex))
WesuranceValidationNEL.success(text)
else
WesuranceValidationNEL.failure("The text can't contain URLs")
}
def validateCommunityName(name: String): WesuranceValidationNEL[String] = {
val v1 = TextValidation.validatePlainTextWithoutUrls(name)
val v2 = TextValidation.validateTextWithoutHTMLelements(name)
(v1 |@| v2) { case _ => name } leftMap { errors =>
errors.map(error => InvalidParameterValue(s"The community's name is invalid: ${error.msg}") )
}
}
A partir de validaciones pequeñas:
Vamos construyendo validaciones mas grandes:
leftMap es una especie de catch para volver a tirar la "excepción" pero transformándola
def validateTextWithoutHTMLelements(text: String): WesuranceValidationNEL[String] = {
if(text.matches(htmlElemRegex))
WesuranceValidationNEL.failure(s"The text can't contain HTML elements")
else
WesuranceValidationNEL.success(text)
}
def validatePlainTextWithoutUrls(text: String): WesuranceValidationNEL[String] = {
if(!text.matches(urlRegex))
WesuranceValidationNEL.success(text)
else
WesuranceValidationNEL.failure("The text can't contain URLs")
}
def validateCommunityName(name: String): WesuranceValidationNEL[String] = {
val v1 = TextValidation.validatePlainTextWithoutUrls(name)
val v2 = TextValidation.validateTextWithoutHTMLelements(name)
(v1 |@| v2) { case _ => name } leftMap { errors =>
errors.map(error => InvalidParameterValue(s"The community's name is invalid: ${error.msg}") )
}
}
A partir de validaciones pequeñas:
Vamos construyendo validaciones mas grandes:
En caso de que haya un error cambiamos el mensaje para que señale específicamente que se trata de un error con el nombre de la comunidad (y no con un texto cualquiera)
case class NewCommunityData(
name: String,
description: String,
categoryId: Int,
protectionsIds: List[Int],
riskObjectId: Int,
membersNeeded: Int,
casesCovered: Int,
compensation: Float
) {
def validate(): WesuranceValidationNEL[NewCommunityData] = {
(
Community.validateMembersNeeded(membersNeeded) |@|
Community.validateCasesCovered(casesCovered) |@|
Community.validateCompensationValue(compensation) |@|
Community.validateCommunityName(name) |@|
Community.validateCommunityDescription(description)
){ case _ => this }
}
}
Múltiples validaciones independientes
En caso de que todas las validaciones sean exitosas devolvemos un Success(NewCommunityData)