I have a simple slick
query which run on database:
def method(): Future[Either[Error, MyCustomDTO]] =
OptionT(database.run(query))
.map(MyCustomDTO(_))
.toRight(dataNotFound())
.value
The problem is with .toRight
. I would like to map it to sifferent errors depends on what was returned by database. E.g.
case FOREIGN_KEY_CONSTRAINT_VIOLATION.toString => constraintError()
case UNIQUE_CONSTRAINT_VIOLATION.toString => uniqueError()
case _ => dataNotFound()
I tried to do match case
in .toRight()
, but it does not work:
.toRight(error => error.asInstanceOf[PSQLException].getSQLState match { .... })
I'm wondering what is the best possibility to map different errors here in a correct way?
Take a look at the signature of toRight(...)
def toRight[L](left: => L)(implicit F: Functor[F]): EitherT[F, L, A] =
EitherT(cata(Left(left), Right.apply))
both of these take by-name parameter - on other words they are special syntax of () => ...
where () => ...
in definition and ...()
in application are inserted for you. Why? Because on toLeft
/toRight
in OptionT
assume that you are handling the kind of error that is expresses by Option
- that is None
. Since there is no need to pass _: None.type =>
it uses by-name parameter instead.
If you want to handle the error you have to handle it inside F[A]
- by providing the right type class (ApplicativeError[F, Throwable]
/MonadError[F, Throwable]
) which would allow calling handleError
/handleErrorWith
/redeem
etc
// F: ApplicativeError[F, Throwable]
// fa: F[MyCustomDTO]
F.redeem(fa)(a => Right(a), error => Left(error match { ... }))
// or with extension methods for AplicativeError
fa.redeem(a => Right(a), error => Left(error match { ... }))
// asRight is extension method creating Either
fa.map(_.asRight[Error]).handleError(e => Left(...))
since your F
seem to be Future
, and since you have it wrapped in OptionT
I guess it worked better if you did something like:
OptionT(database.run(query))
.map(MyCustomDTO(_).asRight[Error])
.getOrElse(dataNotFound().asLeft[MyCustomDTO])
.handleError { error =>
Left(error.asInstanceOf[PSQLException].getSQLState match {
...
})
}
You could also give up on OptionT
and do:
database.run(query).attemptT // EitherT[Future, Throwable, ...]
.leftMap { error =>
error.asInstanceOf[PSQLException].getSQLState match {
...
}
}
.subflatMap { option =>
option.fold(dataNotFound().asLeft)(MyCustomDTO(_).asRight))
}
.value