Skip to content

Commit 88f64bd

Browse files
committed
add-scala3-enum-put-get-derivation
1 parent 23623fe commit 88f64bd

File tree

9 files changed

+189
-24
lines changed

9 files changed

+189
-24
lines changed

modules/core/src/main/scala-3/doobie/util/GetPlatform.scala

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,23 @@
44

55
package doobie.util
66

7-
trait GetPlatform {}
7+
import scala.deriving.Mirror
8+
import scala.compiletime.constValue
9+
import scala.reflect.Enum
10+
11+
trait GetPlatform {
12+
private def of[A](name: String, cases: List[A], labels: List[String]): Get[A] =
13+
Get[String].temap { caseName =>
14+
labels.indexOf(caseName) match {
15+
case -1 => Left(s"enum $name does not contain case: $caseName")
16+
case i => Right(cases(i))
17+
}
18+
}
19+
20+
inline final def deriveEnumString[A <: Enum](using mirror: Mirror.SumOf[A]): Get[A] =
21+
of(
22+
constValue[mirror.MirroredLabel],
23+
summonSingletonCases[mirror.MirroredElemTypes, A](constValue[mirror.MirroredLabel]),
24+
summonLabels[mirror.MirroredElemLabels]
25+
)
26+
}

modules/core/src/main/scala-3/doobie/util/PutPlatform.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,14 @@
44

55
package doobie.util
66

7-
trait PutPlatform
7+
import scala.compiletime.constValue
8+
import scala.deriving.Mirror
9+
import scala.reflect.Enum
10+
11+
trait PutPlatform {
12+
inline final def deriveEnumString[A <: Enum](using mirror: Mirror.SumOf[A]): Put[A] =
13+
val _ = summonSingletonCases[mirror.MirroredElemTypes, A](constValue[mirror.MirroredLabel])
14+
val labels = summonLabels[mirror.MirroredElemLabels]
15+
16+
Put[String].contramap(a => labels(mirror.ordinal(a)))
17+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) 2013-2020 Rob Norris and Contributors
2+
// This software is licensed under the MIT License (MIT).
3+
// For more information see LICENSE or https://opensource.org/licenses/MIT
4+
5+
package doobie.util
6+
7+
import scala.compiletime.{constValue, erasedValue, error, summonInline}
8+
import scala.deriving.Mirror
9+
10+
private[util] inline final def summonLabels[T <: Tuple]: List[String] =
11+
inline erasedValue[T] match
12+
case _: EmptyTuple => Nil
13+
case _: (t *: ts) => constValue[t].asInstanceOf[String] :: summonLabels[ts]
14+
15+
private[util] inline final def summonSingletonCases[T <: Tuple, A](inline typeName: String): List[A] =
16+
inline erasedValue[T] match
17+
case _: EmptyTuple => Nil
18+
case _: (h *: t) =>
19+
inline summonInline[Mirror.Of[h]] match
20+
case m: Mirror.Singleton => m.fromProduct(EmptyTuple).asInstanceOf[A] :: summonSingletonCases[t, A](typeName)
21+
case m: Mirror =>
22+
error("Enum " + typeName + " contains non singleton case " + constValue[m.MirroredLabel])
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (c) 2013-2020 Rob Norris and Contributors
2+
// This software is licensed under the MIT License (MIT).
3+
// For more information see LICENSE or https://opensource.org/licenses/MIT
4+
5+
package doobie.util
6+
7+
trait GetSuitePlatform {}
8+
9+
trait GetDBSuitePlatform {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (c) 2013-2020 Rob Norris and Contributors
2+
// This software is licensed under the MIT License (MIT).
3+
// For more information see LICENSE or https://opensource.org/licenses/MIT
4+
5+
package doobie.util
6+
7+
trait PutSuitePlatform {}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (c) 2013-2020 Rob Norris and Contributors
2+
// This software is licensed under the MIT License (MIT).
3+
// For more information see LICENSE or https://opensource.org/licenses/MIT
4+
5+
package doobie.util
6+
7+
import Predef.augmentString
8+
import doobie.util.invariant.InvalidValue
9+
10+
enum EnumWithOnlySingletonCases {
11+
case One, Two, Three
12+
}
13+
14+
trait GetSuitePlatform { self: munit.FunSuite =>
15+
16+
enum EnumContainsNonSinletonCase {
17+
case One
18+
case Two(i: Int)
19+
}
20+
21+
sealed trait NotEnum
22+
23+
case object NotEnum1 extends NotEnum
24+
25+
test("Get should not be derived for enum with non singleton case") {
26+
val compileError = compileErrors("Get.deriveEnumString[EnumContainsNonSinletonCase]")
27+
28+
assert(compileError.contains("Enum EnumContainsNonSinletonCase contains non singleton case Two"))
29+
}
30+
31+
test("Get should be derived for enum with only singleton cases") {
32+
Get.deriveEnumString[EnumWithOnlySingletonCases]
33+
}
34+
35+
test("Get should not be derived for sealed trait") {
36+
val compileError = compileErrors("Get.deriveEnumString[NotEnum]")
37+
38+
assert(compileError.contains(
39+
"Type argument GetSuitePlatform.this.NotEnum does not conform to upper bound scala.reflect.Enum"))
40+
}
41+
}
42+
43+
trait GetDBSuitePlatform { self: munit.CatsEffectSuite & TransactorProvider =>
44+
import doobie.syntax.all.*
45+
46+
given Get[EnumWithOnlySingletonCases] = Get.deriveEnumString
47+
48+
test("Get should properly read existing value of enum") {
49+
sql"select 'One'".query[EnumWithOnlySingletonCases].unique.transact(xa).attempt.assertEquals(
50+
Right(
51+
EnumWithOnlySingletonCases.One
52+
)
53+
)
54+
}
55+
56+
test("Get should error reading non existing value of enum") {
57+
sql"select 'One1'".query[EnumWithOnlySingletonCases].unique.transact(xa).attempt.assertEquals(
58+
Left(
59+
InvalidValue(
60+
value = "One1",
61+
reason =
62+
"enum EnumWithOnlySingletonCases does not contain case: One1"
63+
)
64+
)
65+
)
66+
}
67+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) 2013-2020 Rob Norris and Contributors
2+
// This software is licensed under the MIT License (MIT).
3+
// For more information see LICENSE or https://opensource.org/licenses/MIT
4+
5+
package doobie.util
6+
7+
import Predef.augmentString
8+
9+
trait PutSuitePlatform { self: munit.FunSuite =>
10+
11+
enum EnumContainsNonSinletonCase {
12+
case One
13+
case Two(i: Int)
14+
}
15+
16+
enum EnumWithOnlySingletonCases {
17+
case One, Two, Three
18+
}
19+
20+
sealed trait NotEnum
21+
22+
case object NotEnum1 extends NotEnum
23+
24+
test("Put should not be derived for enum with non singleton case") {
25+
val compileError = compileErrors("Put.deriveEnumString[EnumContainsNonSinletonCase]")
26+
27+
assert(compileError.contains("Enum EnumContainsNonSinletonCase contains non singleton case Two"))
28+
}
29+
30+
test("Put should be derived for enum with only singleton cases") {
31+
Put.deriveEnumString[EnumWithOnlySingletonCases]
32+
}
33+
34+
test("Put should not be derived for sealed trait") {
35+
val compileError = compileErrors("Put.deriveEnumString[NotEnum]")
36+
37+
assert(compileError.contains(
38+
"Type argument PutSuitePlatform.this.NotEnum does not conform to upper bound scala.reflect.Enum"))
39+
}
40+
}

modules/core/src/test/scala/doobie/util/GetSuite.scala

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,17 @@ import doobie.enumerated.JdbcType
99
import doobie.testutils.VoidExtensions
1010
import doobie.util.transactor.Transactor
1111

12-
class GetSuite extends munit.CatsEffectSuite {
12+
trait TransactorProvider {
13+
lazy val xa = Transactor.fromDriverManager[IO](
14+
driver = "org.h2.Driver",
15+
url = "jdbc:h2:mem:queryspec;DB_CLOSE_DELAY=-1",
16+
user = "sa",
17+
password = "",
18+
logHandler = None
19+
)
20+
}
21+
22+
class GetSuite extends munit.CatsEffectSuite with GetSuitePlatform {
1323

1424
case class X(x: Int)
1525
case class Q(x: String)
@@ -27,17 +37,9 @@ class GetSuite extends munit.CatsEffectSuite {
2737
final case class Foo(s: String)
2838
final case class Bar(n: Int)
2939

30-
class GetDBSuite extends munit.CatsEffectSuite {
40+
class GetDBSuite extends munit.CatsEffectSuite with TransactorProvider with GetDBSuitePlatform {
3141
import doobie.syntax.all.*
3242

33-
lazy val xa = Transactor.fromDriverManager[IO](
34-
driver = "org.h2.Driver",
35-
url = "jdbc:h2:mem:queryspec;DB_CLOSE_DELAY=-1",
36-
user = "sa",
37-
password = "",
38-
logHandler = None
39-
)
40-
4143
// Both of these will fail at runtime if called with a null value, we check that this is
4244
// avoided below.
4345
implicit val FooMeta: Get[Foo] = Get[String].map(s => Foo(s.toUpperCase))

modules/core/src/test/scala/doobie/util/PutSuite.scala

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44

55
package doobie.util
66

7-
import cats.effect.IO
87
import doobie.testutils.VoidExtensions
9-
import doobie.util.transactor.Transactor
108

11-
class PutSuite extends munit.FunSuite {
9+
class PutSuite extends munit.FunSuite with PutSuitePlatform {
1210
case class X(x: Int)
1311

1412
case class Q(x: String)
@@ -17,14 +15,6 @@ class PutSuite extends munit.FunSuite {
1715

1816
case class Reg2(x: Int)
1917

20-
val xa = Transactor.fromDriverManager[IO](
21-
driver = "org.h2.Driver",
22-
url = "jdbc:h2:mem:queryspec;DB_CLOSE_DELAY=-1",
23-
user = "sa",
24-
password = "",
25-
logHandler = None
26-
)
27-
2818
case class Foo(s: String)
2919

3020
case class Bar(n: Int)
@@ -33,5 +23,4 @@ class PutSuite extends munit.FunSuite {
3323
Put[Int].void
3424
Put[String].void
3525
}
36-
3726
}

0 commit comments

Comments
 (0)