I'm developing a method that is supposed to persist an object, if it passes a list of conditions.
If any (or many) condition fail (or any other kind of error appears), a list with the errors should be returned, if everything goes well, a saved entity should be returned.
I was thinking about something like this (it's pseudocode, of course):
request.body.asJson.map { json =>
json.asOpt[Wine].map { wine =>
wine.save.map { wine =>
Ok(toJson(wine.update).toString)
}.getOrElse { errors => BadRequest(toJson(errors))}
}.getOrElse { BadRequest(toJson(Error("Invalid Wine entity")))}
}.getOrElse { BadRequest(toJson(Error("Expecting JSON data")))}
That is, I'd like to treat it like an Option[T], that if any validation fails, instead of returning None
it gives me the list of errors...
The idea is to return an array of JSON errors...
So the question would be, is this the right way to handle these kind of situation? And what would be the way to accomplish it in Scala?
--
Oops, just posted the question and discovered Either
http://www.scala-lang.org/api/current/scala/Either.html
Anyway, I'd like to know what you think about the chosen approach, and if there's any other better alternative to handle it.
If you need an empty value, instead of using
Either[A,Option[B]]
you can use liftBox
, which can have three values:Full
(there is a valid result)Empty
(no result, but no error either)Failure
(an error happened)Box
are more flexible thanEither
thanks to a rich API. Of course, although they were created for Lift, you can use them in any other framework.Using scalaz you have
Validation[E, A]
, which is likeEither[E, A]
but has the property that ifE
is a semigroup (meaning things that can be concatenated, like lists) than multiple validated results can be combined in a way that keeps all the errors that occured.Using Scala 2.10-M6 and Scalaz 7.0.0-M2 for example, where Scalaz has a custom
Either[L, R]
named\/[L, R]
which is right-biased by default:Here
result
is aValidation[NonEmptyList[Throwable], String]
, either containing all the errors occured (temp sensor error and/or twitter erroror none) or the successful message. You can then switch back to\/
for convenience.Note: The difference between Either and Validation is mainly that with Validation you can accumulate errors, but cannot
flatMap
to lose the accumulated errors, while with Either you can't (easily) accumulate but canflatMap
(or in a for-comprehension) and possibly lose all but the first error message.About error hierarchies
I think this might be of interest for you. Regardless of using scalaz/
Either
/\/
/Validation
, I experienced that getting started was easy but going forward needs some additional work. The problem is, how do you collect errors from multiple erring functions in a meaningful way? Sure, you can just useThrowable
orList[String]
everywhere and have an easy time, but doesn't sound too much usable or interpretable. Imagine getting a list of errors like "child age missing" :: "IO error reading file" :: "division by zero".So my choice is to create error hierarchies (using ADT-s), just like as one would wrap checked exceptions of Java into hierarchies. For example:
Then when using result of erring functions with different error types, I join them under a relevant parent error type.
Here
f <-: e
maps the functionf
on the left ofe: \/
since\/
is a Bifunctor. Forse: scala.Either
you might havese.left.map(f)
.This may be further improved by providing shapeless
HListIso
s to be able to draw nice error trees.Revisions
Updated:
(e: \/).vnel
lifts the failure side into aNonEmptyList
so if we have a failure we have at least one error (was: or none).If you have
Option
values, and you want to turn them into success/failure values, you can turn anOption
into anEither
using thetoLeft
ortoRight
method.Usually a
Right
represents success, so useo.toRight("error message")
to turnSome(value)
intoRight(value)
andNone
intoLeft("error message")
.Unfortunately Scala doesn't recognise this right-bias by default, so you have to jump through a hoop (by calling the
.right
method) in order to neatly compose yourEither
s in a for-comprehension.well, this is my attemp using Either
And wine.save is like the following:
Validate checks several preconditions. I still have to add a try/catch to catch any db error
I'm still looking for a way to improve the whole thing, it feels much to verbose to my taste...