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 :
Similaire à Try, mais asynchrone
val f: Future[T] = ??? val value: Option[Try[T]] = f.value
import 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
GitHub : propensive/rapture-core
Utiliser correctement ces types pour gérer les erreurs permet :
Twitter : @d_sferruzza
Slides sur GitHub :