Поскольку вы хотите NonEmptyList
ошибок, кажется, что вам нужно объединить результаты getUserByUsername
и getUserByEmail
с Validated
, и только потом преобразовать их в Either
. На этом Either
вы можете затем вызвать fold
с некоторыми IO
в обеих ветвях. Было слишком неудобно объединять его в одно for
-понимание, поэтому я разделил его на два метода:
import cats.data.Validated.condNel
import cats.data.NonEmptyList
import cats.syntax.apply._
import cats.syntax.either._
import cats.effect._
case class User(name: String)
trait CreateUserOnlyIfNoCollision {
type Username = String
type Email = String
type Password = String
type ErrorMsg = String
type UserId = Long
def createUser(username: Username, email: Email, password: Password): IO[UserId]
def getUserByUsername(username: Username): IO[Option[User]]
def getUserByEmail(email: Email): IO[Option[User]]
/** Attempts to get user both by name and by email,
* returns `()` if nothing is found, otherwise
* returns a list of error messages that tell whether
* name and/or address are already in use.
*/
def checkUnused(username: Username, email: Email)
: IO[Either[NonEmptyList[String], Unit]] = {
for {
o1 <- getUserByUsername(username)
o2 <- getUserByEmail(email)
} yield {
(
condNel(o1.isEmpty, (), "username is already in use"),
condNel(o2.isEmpty, (), "email is already in use")
).mapN((_, _) => ()).toEither
}
}
/** Attempts to register a user.
*
* Returns a new `UserId` in case of success, or
* a list of errors if the name and/or address are already in use.
*/
def registerUser(username: Username, email: Email, password: Password)
: IO[Either[NonEmptyList[String], UserId]] = {
for {
e <- checkUnused(username, email)
res <- e.fold(
errors => IO.pure(errors.asLeft),
_ => createUser(username, email, password).map(_.asRight)
)
} yield res
}
}
Нечто подобное может быть?
Или, альтернативно, с EitherT
:
def registerUser(username: Username, email: Email, password: Password)
: IO[Either[Nel[String], UserId]] = {
(for {
e <- EitherT(checkUnused(username, email))
res <- EitherT.liftF[IO, Nel[String], UserId](
createUser(username, email, password)
)
} yield res).value
}
или
def registerUser(username: Username, email: Email, password: Password)
: IO[Either[Nel[String], UserId]] = {
(for {
e <- EitherT(checkUnused(username, email))
res <- EitherT(
createUser(username, email, password).map(_.asRight[Nel[String]])
)
} yield res).value
}