From c3551b7ea07121fa67c286a34a6dd230f1c55466 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Tue, 17 Nov 2020 14:13:01 +0200 Subject: [PATCH] Add VersionedModelReader and VersionedModelWriter constructors (#72) It's quite inconvenient in user code not to have constructors. --- build.sbt | 2 +- .../teleproto/VersionedModelReader.scala | 42 +++++++++++++----- .../teleproto/VersionedModelWriter.scala | 44 ++++++++++++++----- .../ProtocolBuffersRoundTripTest.scala | 24 ++++++++-- 4 files changed, 87 insertions(+), 25 deletions(-) diff --git a/build.sbt b/build.sbt index 956a4ab..db217d1 100644 --- a/build.sbt +++ b/build.sbt @@ -15,7 +15,7 @@ lazy val `teleproto` = .settings(Project.inConfig(Test)(sbtprotoc.ProtocPlugin.protobufConfigSettings): _*) .settings( name := "teleproto", - version := "1.9.0", + version := "1.10.0", libraryDependencies ++= Seq( library.scalaPB % "protobuf", library.scalaPBJson % Compile, diff --git a/src/main/scala/io/moia/protos/teleproto/VersionedModelReader.scala b/src/main/scala/io/moia/protos/teleproto/VersionedModelReader.scala index fa82840..cc6c4a8 100644 --- a/src/main/scala/io/moia/protos/teleproto/VersionedModelReader.scala +++ b/src/main/scala/io/moia/protos/teleproto/VersionedModelReader.scala @@ -40,16 +40,13 @@ import scala.util.{Failure, Success, Try} * Usage Example: * * {{{ - * implicit val someModelReader: ModelReader[SomeDetachedModel] = - * new ModelReader[MyVersion, SomeDetachedModel] { - * val readerMappings: ReaderMappings = - * ListMap( - * VN -> readerMapping(vN.SomeApiModel), - * ... - * V2 -> readerMapping(v2.SomeApiModel), - * V1 -> readerMapping(v1.SomeApiModel) - * ) - * } + * implicit val someModelReader: VersionedModelReader[SomeDetachedModel] = + * VersionedModelReader[MyVersion, SomeDetachedModel]( + * VN -> vN.SomeApiModel, + * ... + * V2 -> v2.SomeApiModel, + * V1 -> v1.SomeApiModel + * ) * }}} */ trait VersionedModelReader[Version, DetachedModel] { @@ -153,4 +150,29 @@ object VersionedModelReader { */ def fromJson(jsonString: String, parser: Parser): Try[PbResult[Model]] = fromJson(jsonString) } + + def apply[Version, DetachedModel]( + readers: (Version, CompanionReader[DetachedModel])* + ): VersionedModelReader[Version, DetachedModel] = new VersionedModelReader[Version, DetachedModel] { + val readerMappings: ReaderMappings = ListMap(readers.map { case (v, r) => r.versioned(v)(this) }: _*) + } + + sealed trait CompanionReader[DetachedModel] { + type SpecificModel <: GeneratedMessage + def companion: GeneratedMessageCompanion[SpecificModel] + implicit def reader: Reader[SpecificModel, DetachedModel] + + final def versioned[V](version: V)(modelReader: VersionedModelReader[V, DetachedModel]): (V, modelReader.VersionReader) = + version -> modelReader.readerMapping(companion) + } + + object CompanionReader { + implicit def fromCompanion[P <: GeneratedMessage, M](gmc: GeneratedMessageCompanion[P])( + implicit rpm: Reader[P, M] + ): CompanionReader[M] = new CompanionReader[M] { + type SpecificModel = P + def companion: GeneratedMessageCompanion[P] = gmc + def reader: Reader[P, M] = rpm + } + } } diff --git a/src/main/scala/io/moia/protos/teleproto/VersionedModelWriter.scala b/src/main/scala/io/moia/protos/teleproto/VersionedModelWriter.scala index 77095de..182a9de 100644 --- a/src/main/scala/io/moia/protos/teleproto/VersionedModelWriter.scala +++ b/src/main/scala/io/moia/protos/teleproto/VersionedModelWriter.scala @@ -37,16 +37,13 @@ import scala.util.{Failure, Success, Try} * Usage Example: * * {{{ - * implicit val someModelWriter: ModelWriter[SomeDetachedModel] = - * new ModelWriter[MyVersion, SomeDetachedModel] { - * val writerMappings: WriterMappings = - * ListMap( - * VN -> writerMapping(vN.SomeApiModel), - * ... - * V2 -> writerMapping(v2.SomeApiModel), - * V1 -> writerMapping(v1.SomeApiModel) - * ) - * } + * implicit val someModelWriter: VersionedModelWriter[SomeDetachedModel] = + * VersionedModelWriter[MyVersion, SomeDetachedModel]( + * VN -> vN.SomeApiModel, + * ... + * V2 -> v2.SomeApiModel, + * V1 -> v1.SomeApiModel + * ) * }}} */ trait VersionedModelWriter[Version, DetachedModel] { @@ -120,6 +117,31 @@ trait VersionedModelWriter[Version, DetachedModel] { } object VersionedModelWriter { - private val printer = new Printer().includingDefaultValueFields.formattingLongAsNumber + + def apply[Version, DetachedModel]( + writers: (Version, CompanionWriter[DetachedModel])* + ): VersionedModelWriter[Version, DetachedModel] = new VersionedModelWriter[Version, DetachedModel] { + val writerMappings: WriterMappings = ListMap(writers.map { case (v, w) => w.versioned(v) }: _*) + } + + sealed trait CompanionWriter[DetachedModel] { + type SpecificModel <: GeneratedMessage + def writer: Writer[DetachedModel, SpecificModel] + + final def versioned[V](version: V): (V, Writer[DetachedModel, GeneratedMessage]) = + version -> writer + } + + object CompanionWriter { + implicit def fromCompanion[P <: GeneratedMessage, M](gmc: GeneratedMessageCompanion[P])( + implicit wmp: Writer[M, P] + ): CompanionWriter[M] = { + val _ = gmc + new CompanionWriter[M] { + type SpecificModel = P + def writer: Writer[M, P] = wmp + } + } + } } diff --git a/src/test/scala/io/moia/protos/teleproto/ProtocolBuffersRoundTripTest.scala b/src/test/scala/io/moia/protos/teleproto/ProtocolBuffersRoundTripTest.scala index 8ab7eda..48dda37 100644 --- a/src/test/scala/io/moia/protos/teleproto/ProtocolBuffersRoundTripTest.scala +++ b/src/test/scala/io/moia/protos/teleproto/ProtocolBuffersRoundTripTest.scala @@ -4,11 +4,17 @@ import io.moia.food.food import org.scalacheck.Gen import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import scala.util.Success + class ProtocolBuffersRoundTripTest extends UnitTest with ScalaCheckPropertyChecks { import ProtocolBuffersRoundTripTest._ - val mealReader: Reader[food.Meal, Meal] = ProtocolBuffers.reader[food.Meal, Meal] - val mealWriter: Writer[Meal, food.Meal] = ProtocolBuffers.writer[Meal, food.Meal] + implicit val reader: Reader[food.Meal, Meal] = ProtocolBuffers.reader[food.Meal, Meal] + implicit val writer: Writer[Meal, food.Meal] = ProtocolBuffers.writer[Meal, food.Meal] + + val version = 1 + val modelReader = VersionedModelReader[Int, Meal](version -> food.Meal) + val modelWriter = VersionedModelWriter[Int, Meal](version -> food.Meal) val colorGen: Gen[Color] = Gen.oneOf(Color.Red, Color.Orange, Color.Yellow, Color.Pink, Color.Blue) @@ -37,7 +43,19 @@ class ProtocolBuffersRoundTripTest extends UnitTest with ScalaCheckPropertyCheck "ProtocolBuffers" should { "generate writer and reader that round trip successfully" in { forAll(mealGen) { meal => - mealReader.read(mealWriter.write(meal)) shouldBe PbSuccess(meal) + reader.read(writer.write(meal)) shouldBe PbSuccess(meal) + } + } + + "create model writer and reader that round trip successfully via JSON" in { + forAll(mealGen) { meal => + modelWriter.toJson(meal, version).flatMap(modelReader.fromJson(_, version)) shouldBe Success(PbSuccess(meal)) + } + } + + "create model writer and reader that round trip successfully via Protocol Buffers" in { + forAll(mealGen) { meal => + modelWriter.toByteArray(meal, version).flatMap(modelReader.fromProto(_, version)) shouldBe Success(PbSuccess(meal)) } } }