Bracket
instance
Bracket
for any MonadError
type ValidationResult[A] = ValidatedNec[JobPostError, A]
override def createJobPost(
newJobPost: JobPost
): IO[ValidatedNec[String, JobPost]] = {
// TODO: Add logger here on every field validation
val localValidation: ValidationResult[JobPost] = (
validateNoop(newJobPost.id),
validateEmptiness("Job post title".some, newJobPost.title),
validateEmptiness("Job post description".some,
newJobPost.description),
validateNoop(newJobPost.createdAt),
validateNoop(newJobPost.modifiedAt)
).mapN(JobPost)
val databaseValidations: IO[ValidationResult[JobPost]] = (for {
_ <- Logger[ConnectionIO].info("Validating job title")
title <- validateTitleExistence(newJobPost)
_ <- Logger[ConnectionIO].info("Validating job description")
description <- validateNoop(newJobPost.description).pure[ConnectionIO]
createdAt <- validateNoop(newJobPost.createdAt).pure[ConnectionIO]
modifiedAt <- validateNoop(newJobPost.modifiedAt).pure[ConnectionIO]
id <- validateNoop(newJobPost.id).pure[ConnectionIO]
} yield {
(id, title, description, createdAt, modifiedAt).mapN(JobPost)
}).transact(xa)
val response: IO[ValidatedNec[String, JobPost]] =
localValidation match {
case Valid(_) => databaseValidations.map(_.leftMap(_.map(_.errorMessage)))
case Invalid(e) => e.map(_.errorMessage).invalid[JobPost].pure[IO]
}
response
}
Valid
case in your result, so you don't even need the last clause
Band
IO.parSequence
or parSequenceN
. It's not immediately obvious where that behavior lives if you're using IO
directly and not the typeclasses. Even with the typeclasses, parSequence
and parSequenceN
for example exist in different typeclasses, and it's not necessarily obvious to a newcomer why that might be
IO
and some cases like that make things less discoverable than I'd want it to be
IO
as direct methods; things like >>
or adaptError
cats.implicits._
import in order to get the "full" API for IO
is a little unfortunate I think
Semilattice
or Band
, for that matter.
Is there any sort of stance on adding "duplicate" methods in more discoverable locations?
I think the idea was to keep it as minimal as possible to ease maintenance.
I don't know what's the current stance on this. In my opinion methods on IO
improve user experience and allow optimization opportunities. People not using IntelliJ (or with better computer) can argue discoverability part because they can always import cats.implicits._
but that's not the case with me. IMO methods on IO
+ passing thread pool only at execution/evalOn (coming in 3.x I suppose) are two things that are really convenient for users.
I don't know what's the current stance on this. In my opinion methods on IO improve user experience and allow optimization opportunities. People not using IntelliJ (or with better computer) can argue discoverability part because they can always import cats.implicits._ but that's not the case with me. IMO methods on IO + passing thread pool only at execution/evalOn (coming in 3.x I suppose) are two things that are really convenient for users.
This is my view of things. IMO IO
should have specific versions of most of the common functions, even if we can inherit them from typeclasses, and the typeclass instances should override to point to our duplicates