Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare protobufs module for future publishing and adding scalapb build-in instances #389

Merged
merged 2 commits into from
Sep 25, 2023
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
10 changes: 6 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ val ciCommand = (platform: String, scalaSuffix: String) => {
def withCoverage(tasks: String*): Vector[String] =
"coverage" +: tasks.toVector :+ "coverageAggregate" :+ "coverageOff"

val projects = Vector("chimney", "chimneyCats", "protobufs")
val projects = Vector("chimney", "chimneyCats", "chimneyProtobufs")
.map(name => s"$name${if (isJVM) "" else platform}$scalaSuffix")
def tasksOf(name: String): Vector[String] = projects.map(project => s"$project/$name")

Expand All @@ -260,7 +260,7 @@ lazy val root = project
.settings(publishSettings)
.settings(noPublishSettings)
.aggregate(
(chimneyMacroCommons.projectRefs ++ chimney.projectRefs ++ chimneyCats.projectRefs ++ protobufs.projectRefs)*
(chimneyMacroCommons.projectRefs ++ chimney.projectRefs ++ chimneyCats.projectRefs ++ chimneyProtobufs.projectRefs)*
)
.settings(
moduleName := "chimney-build",
Expand Down Expand Up @@ -371,8 +371,8 @@ lazy val chimneyCats = projectMatrix
.settings(libraryDependencies += "org.typelevel" %%% "cats-core" % "2.10.0" % "provided")
.dependsOn(chimney % "test->test;compile->compile")

lazy val protobufs = projectMatrix
.in(file("protobufs"))
lazy val chimneyProtobufs = projectMatrix
.in(file("chimney-protobufs"))
.someVariations(versions.scalas, versions.platforms)(only1VersionInIDE*)
.enablePlugins(GitVersioning, GitBranchPrompt)
.disablePlugins(WelcomePlugin)
Expand All @@ -391,6 +391,8 @@ lazy val protobufs = projectMatrix
else Seq.empty
},
Compile / PB.targets := Seq(scalapb.gen() -> (Compile / sourceManaged).value / "scalapb"),
Test / PB.protoSources += PB.externalSourcePath.value,
Test / PB.targets := Seq(scalapb.gen() -> (Test / sourceManaged).value / "scalapb"),
libraryDependencies += "com.thesamet.scalapb" %%% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion % "protobuf",
mimaFailOnNoPrevious := false
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.scalaland.chimney.protobufs

import io.scalaland.chimney.{partial, PartialTransformer}

// TODO: not yet published
trait ProtobufsPartialTransformerImplicits {

final type IsEmpty = scalapb.GeneratedOneof { type ValueType = Nothing }
// TODO: not yet published
implicit def partialTransformerEmptyOneOfInstance[From <: IsEmpty, To]: PartialTransformer[From, To] =
PartialTransformer(_ => partial.Result.fromEmpty)

Check warning on line 11 in chimney-protobufs/src/main/scala/io/scalaland/chimney/protobufs/ProtobufsPartialTransformerImplicits.scala

View check run for this annotation

Codecov / codecov/patch

chimney-protobufs/src/main/scala/io/scalaland/chimney/protobufs/ProtobufsPartialTransformerImplicits.scala#L9-L11

Added lines #L9 - L11 were not covered by tests
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.scalaland.chimney

// TODO: not yet published
package object protobufs extends ProtobufsPartialTransformerImplicits
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.scalaland.chimney

// format: off
import io.scalaland.chimney.dsl._
// format: on
import io.scalaland.chimney.examples.pb
import io.scalaland.chimney.fixtures.{addressbook, order}

class ProtobufBuildInSpec extends ChimneySpec {

test("transform value classes between their primitive representations") {

addressbook.PersonName("John").transformInto[String] ==> "John"
addressbook.PersonId(5).transformInto[Int] ==> 5
addressbook.Email("[email protected]").transformInto[String] ==> "[email protected]"
}

test("not compile if target type is wrong for value class") {

compileErrorsFixed(""" addressbook.PersonName("John").transformInto[Int] """)
.check(
"Chimney can't derive transformation from io.scalaland.chimney.fixtures.addressbook.PersonName to scala.Int"
)

compileErrorsFixed(""" addressbook.PersonId(5).transformInto[String] """)
.check(
"Chimney can't derive transformation from io.scalaland.chimney.fixtures.addressbook.PersonId to java.lang.String"
)

compileErrorsFixed(""" addressbook.Email("[email protected]").transformInto[Float] """)
.check(
"Chimney can't derive transformation from io.scalaland.chimney.fixtures.addressbook.Email to scala.Float"
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.scalaland.chimney

// format: off
import io.scalaland.chimney.dsl._
// format: on
import io.scalaland.chimney.examples.pb
import io.scalaland.chimney.fixtures.{addressbook, order}

class ProtobufEnumSpec extends ChimneySpec {

test("transform enum represented as sealed trait hierarchy") {

(addressbook.MOBILE: addressbook.PhoneType)
.transformInto[pb.addressbook.PhoneType] ==> pb.addressbook.PhoneType.MOBILE
(addressbook.HOME: addressbook.PhoneType).transformInto[pb.addressbook.PhoneType] ==> pb.addressbook.PhoneType.HOME
(addressbook.WORK: addressbook.PhoneType).transformInto[pb.addressbook.PhoneType] ==> pb.addressbook.PhoneType.WORK
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,7 @@ import io.scalaland.chimney.dsl._
import io.scalaland.chimney.examples.pb
import io.scalaland.chimney.fixtures.{addressbook, order}

class PBTransformationSpec extends ChimneySpec {

test("transform value classes between their primitive representations") {

addressbook.PersonName("John").transformInto[String] ==> "John"
addressbook.PersonId(5).transformInto[Int] ==> 5
addressbook.Email("[email protected]").transformInto[String] ==> "[email protected]"
}

test("not compile if target type is wrong for value class") {

compileErrorsFixed(""" addressbook.PersonName("John").transformInto[Int] """)
.check(
"Chimney can't derive transformation from io.scalaland.chimney.fixtures.addressbook.PersonName to scala.Int"
)

compileErrorsFixed(""" addressbook.PersonId(5).transformInto[String] """)
.check(
"Chimney can't derive transformation from io.scalaland.chimney.fixtures.addressbook.PersonId to java.lang.String"
)

compileErrorsFixed(""" addressbook.Email("[email protected]").transformInto[Float] """)
.check(
"Chimney can't derive transformation from io.scalaland.chimney.fixtures.addressbook.Email to scala.Float"
)
}

test("transform enum represented as sealed trait hierarchy") {

(addressbook.MOBILE: addressbook.PhoneType)
.transformInto[pb.addressbook.PhoneType] ==> pb.addressbook.PhoneType.MOBILE
(addressbook.HOME: addressbook.PhoneType).transformInto[pb.addressbook.PhoneType] ==> pb.addressbook.PhoneType.HOME
(addressbook.WORK: addressbook.PhoneType).transformInto[pb.addressbook.PhoneType] ==> pb.addressbook.PhoneType.WORK
}
class ProtobufMessageSpec extends ChimneySpec {

group("transform bigger case classes") {

Expand Down Expand Up @@ -172,58 +139,4 @@ class PBTransformationSpec extends ChimneySpec {
}
}
}

group("transformer sealed traits generated from oneof") {

object HandleEmptyAutomatically {
// TODO: consider creating some protobuf integration module in the future
type IsEmpty = scalapb.GeneratedOneof { type ValueType = Nothing }
implicit def handleEmptyInstance[From <: IsEmpty, To]: PartialTransformer[From, To] =
PartialTransformer(_ => partial.Result.fromEmpty)
}

test("AddressBookType (oneof value - sealed contains single-value wrappers around actual products)") {
val domainType: addressbook.AddressBookType = addressbook.AddressBookType.Private("test")
val pbType: pb.addressbook.AddressBookType =
pb.addressbook.AddressBookType.of(
pb.addressbook.AddressBookType.Value.Private(pb.addressbook.AddressBookType.Private.of("test"))
)

domainType.into[pb.addressbook.AddressBookType.Value].transform ==> pbType.value

pbType.value
.intoPartial[addressbook.AddressBookType]
.withCoproductInstancePartial[pb.addressbook.AddressBookType.Value.Empty.type](_ => partial.Result.fromEmpty)
.transform
.asOption ==> Some(domainType)
locally {
// format: off
import HandleEmptyAutomatically._
// format: on
pbType.value.intoPartial[addressbook.AddressBookType].transform.asOption ==> Some(domainType)
}
}

test("CustomerStatus (oneof sealed_value - flat representation with additional Empty vase)") {
val domainStatus: order.CustomerStatus = order.CustomerStatus.CustomerRegistered
val pbStatus: pb.order.CustomerStatus = pb.order.CustomerRegistered()

domainStatus.into[pb.order.CustomerStatus].transform ==> pbStatus

pbStatus
.intoPartial[order.CustomerStatus]
.withCoproductInstancePartial[pb.order.CustomerStatus.Empty.type](_ => partial.Result.fromEmpty)
.withCoproductInstance[pb.order.CustomerStatus.NonEmpty](_.transformInto[order.CustomerStatus])
.transform
.asOption ==> Some(domainStatus)
}

test("PaymentStatus (oneof sealed_value_optional - flat representation wrapped in Option)") {
val domainStatus: Option[order.PaymentStatus] = Option(order.PaymentStatus.PaymentRequested)
val pbStatus: Option[pb.order.PaymentStatus] = Option(pb.order.PaymentRequested())

domainStatus.into[Option[pb.order.PaymentStatus]].transform ==> pbStatus
pbStatus.into[Option[order.PaymentStatus]].transform ==> domainStatus
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.scalaland.chimney

// format: off
import io.scalaland.chimney.dsl._
// format: on
import io.scalaland.chimney.examples.pb
import io.scalaland.chimney.fixtures.{addressbook, order}

class ProtobufOneOfSpec extends ChimneySpec {

group("transformer sealed traits generated from oneof") {

test("AddressBookType (oneof value - sealed contains single-value wrappers around actual products)") {
val domainType: addressbook.AddressBookType = addressbook.AddressBookType.Private("test")
val pbType: pb.addressbook.AddressBookType =
pb.addressbook.AddressBookType.of(
pb.addressbook.AddressBookType.Value.Private(pb.addressbook.AddressBookType.Private.of("test"))
)

domainType.into[pb.addressbook.AddressBookType.Value].transform ==> pbType.value

pbType.value
.intoPartial[addressbook.AddressBookType]
.withCoproductInstancePartial[pb.addressbook.AddressBookType.Value.Empty.type](_ => partial.Result.fromEmpty)
.transform
.asOption ==> Some(domainType)
locally {
// format: off
import protobufs._
// format: on
pbType.value.intoPartial[addressbook.AddressBookType].transform.asOption ==> Some(domainType)
}
}

test("CustomerStatus (oneof sealed_value - flat representation with additional Empty vase)") {
val domainStatus: order.CustomerStatus = order.CustomerStatus.CustomerRegistered
val pbStatus: pb.order.CustomerStatus = pb.order.CustomerRegistered()

domainStatus.into[pb.order.CustomerStatus].transform ==> pbStatus

pbStatus
.intoPartial[order.CustomerStatus]
.withCoproductInstancePartial[pb.order.CustomerStatus.Empty.type](_ => partial.Result.fromEmpty)
.withCoproductInstance[pb.order.CustomerStatus.NonEmpty](_.transformInto[order.CustomerStatus])
.transform
.asOption ==> Some(domainStatus)
}

test("PaymentStatus (oneof sealed_value_optional - flat representation wrapped in Option)") {
val domainStatus: Option[order.PaymentStatus] = Option(order.PaymentStatus.PaymentRequested)
val pbStatus: Option[pb.order.PaymentStatus] = Option(pb.order.PaymentRequested())

domainStatus.into[Option[pb.order.PaymentStatus]].transform ==> pbStatus
pbStatus.into[Option[order.PaymentStatus]].transform ==> domainStatus
}
}
}