On veut valider un utilisateur qui s'inscrit.
case class User(id: UUID,
bio: String,
birthday: String)
case class CheckedUser(id: UUID,
bio: String,
birthday: DateTime)
def validate(user: User): CheckedUser
case class User(id: UUID,
bio: String,
birthday: String)
3 échecs possibles :
def validate(user: User): CheckedUser
def validate(user: User): Option[CheckedUser] = {
// Si tout est ok
Some(checkedUser)
// Sinon
None
}
Suffisant si on n'a pas besoin d'information sur l'erreur
val checkedUser: Option[CheckedUser] = ???
// Pas bien (erreur de compilation)
checkedUser.id
// Pas bien (peut lancer une exception)
checkedUser.get.id
// Bien
checkedUser match {
case Some(u) => u.id
case None => ???
// Obligé de traiter le cas où ça échoue
}
// Validation Error
sealed trait VE
case class BioTooLong(length: Int) extends VE
case object InvalidBirthdayFormat extends VE
case object ImpossibleBirthday extends VE
sealed aide le compilateur à vérifier tous les cas possibles
def validate(user: User):
Either[VE, CheckedUser] = {
// Si tout est ok
Right(checkedUser)
// Sinon
Left(error)
}
val checkedUser: Either[VE, CheckedUser] = ???
checkedUser match {
case Right(u) => u.id
case Left(BioTooLong(length)) => ???
case Left(InvalidBirthdayFormat) => ???
case Left(ImpossibleBirthday) => ???
// Warning du compilateur si on oublie un cas
}
def validate(user: User): Try[CheckedUser] = {
// Si tout est ok
Success(checkedUser)
// Sinon
Failure(error)
}
Similaire à Either :
Try[T] ~= Either[Throwable, T]
Throwable
)Similaire à Try, mais asynchrone
val f: Future[T] = ???
val value: Option[Try[T]] = f.value
None
si pas encore complétéSome(Success(t))
si complété avec succèsSome(Failure(error))
si complété avec une erreurimport scalaz.{\/, -\/, \/-}
def validate(user: User): VE \/ CheckedUser = {
// Si tout est ok
\/-(checkedUser)
// Sinon
-\/(error)
}
Similaire à Either, mais part du principe que la valeur intéressante est à droite (right-biased)
eitherVal.right.map(???)
disjunctionVal.map(???)
import scalaz.{ValidationNel, Success, Failure}
import scalaz.syntax.validation._
import scalaz.syntax.applicative._
def checkBioLength(u: User):
ValidationNel[VE, User] = {
val l = u.bio.length
if (l < 5) u.success
else BioTooLong(l).failureNel
}
def checkBirthdayFormat(u: User):
ValidationNel[VE, User] = ???
def validate(user: User):
ValidationNel[VE, CheckedUser] = {
val cbl = checkBioLength(user)
val cbf = checkBirthdayFormat(user)
(cbl |@| cbf) { (u, _) =>
CheckedUser(???)
}
}
Permet d'accumuler les erreurs lorsqu'on fait des validations indépendantes
import modes.returnEither._
)Either
!GitHub : propensive/rapture-core
Utiliser correctement ces types pour gérer les erreurs permet :
Twitter : @d_sferruzza
Slides sur GitHub :