Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion modules/core/src/main/scala/doobie/util/read.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

package doobie.util

import cats.Applicative
import cats.{Applicative, Show}
import doobie.ResultSetIO
import doobie.enumerated.Nullability
import doobie.enumerated.Nullability.{NoNulls, NullabilityKnown}
import doobie.free.resultset as IFRS
import doobie.util.invariant.InvalidValue

import java.sql.ResultSet
import scala.annotation.implicitNotFound
Expand Down Expand Up @@ -58,6 +59,16 @@ trait Read[A] {
final def ap[B](ff: Read[A => B]): Read[B] = {
new Read.Composite[B, A => B, A](ff, this, (f, a) => f(a))
}

/** Equivalent to `map`, but allows the conversion to fail with an error message.
*/
final def emap[B](f: A => Either[String, B])(implicit sA: Show[A]): Read[B] =
map { a =>
f(a) match {
case Left(reason) => throw InvalidValue[A, B](a, reason)
case Right(b) => b
}
}
}

object Read extends ReadPlatform {
Expand Down
27 changes: 27 additions & 0 deletions modules/core/src/test/scala/doobie/util/ReadSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package doobie.util

import cats.Show
import cats.effect.IO
import doobie.util.TestTypes.*
import doobie.util.transactor.Transactor
Expand Down Expand Up @@ -155,6 +156,32 @@ class ReadSuite extends munit.CatsEffectSuite with ReadSuitePlatform {
insertTuple3AndCheckRead((1, "s1", "s2"), WrappedSimpleCaseClass(SimpleCaseClass(Some(1), "custom", Some("s2"))))
}

test(".emap should correctly transform the value") {
import doobie.implicits.*
implicit val s: Show[SimpleCaseClass] = _.toString
implicit val r: Read[WrappedSimpleCaseClass] = Read[SimpleCaseClass].emap(s =>
Right(WrappedSimpleCaseClass(
s.copy(s = "custom")
)))

insertTuple3AndCheckRead((1, "s1", "s2"), WrappedSimpleCaseClass(SimpleCaseClass(Some(1), "custom", Some("s2"))))
}

test(".emap should fail a transform") {
import doobie.implicits.*
implicit val s: Show[SimpleCaseClass] = _.toString
implicit val r: Read[WrappedSimpleCaseClass] = Read[SimpleCaseClass].emap(_ =>
Left("Invalid transformation")
)

sql"SELECT 1,'a','b'".query[WrappedSimpleCaseClass].unique.transact(xa).attempt.assertEquals(
Left(doobie.util.invariant.InvalidValue[SimpleCaseClass, WrappedSimpleCaseClass](
SimpleCaseClass(Some(1), "a", Some("b")),
"Invalid transformation")
)
)
}

/*
case class with nested Option case class field
*/
Expand Down