Skip to content

Commit

Permalink
Add VersionedModelReader and VersionedModelWriter constructors (#72)
Browse files Browse the repository at this point in the history
It's quite inconvenient in user code not to have constructors.
  • Loading branch information
Georgi Krastev authored Nov 17, 2020
1 parent 63671e4 commit c3551b7
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 25 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
42 changes: 32 additions & 10 deletions src/main/scala/io/moia/protos/teleproto/VersionedModelReader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand Down Expand Up @@ -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
}
}
}
44 changes: 33 additions & 11 deletions src/main/scala/io/moia/protos/teleproto/VersionedModelWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand Down Expand Up @@ -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
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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))
}
}
}
Expand Down

0 comments on commit c3551b7

Please sign in to comment.