Skip to content

Commit

Permalink
semiauto derivation working
Browse files Browse the repository at this point in the history
  • Loading branch information
jtjeferreira committed Aug 10, 2018
1 parent ebf325e commit c22eb9f
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 76 deletions.
5 changes: 4 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ version := "0.1"

scalaVersion := "2.11.12"

libraryDependencies += "com.propensive" %% "magnolia" % "0.8.0"
libraryDependencies += "com.propensive" %% "magnolia" % "0.9.0"
libraryDependencies += "org.reactivemongo" %% "reactivemongo-bson" % "0.12.6"

//scalacOptions += "-Xlog-implicits"
//scalacOptions += "-Ymacro-debug-lite"
56 changes: 0 additions & 56 deletions src/main/scala/BSONDerivation.scala

This file was deleted.

81 changes: 81 additions & 0 deletions src/main/scala/magnolia/bson/BSONDerivation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package magnolia.bson

import magnolia._
import reactivemongo.bson
import reactivemongo.bson._

import scala.util.{Failure, Success}

//case class Exported[T](instance: T) extends AnyVal
//object Exported {
// implicit final def a[A](implicit reader: BSONReader[_ <: BSONValue, A]): Exported[BSONReader[_ <: BSONValue, A]] = Exported(reader)
// implicit final def b[A](implicit reader: BSONWriter[A, _ <: BSONValue]): Exported[BSONWriter[A, _ <: BSONValue]] = Exported(reader)
//}

object BSONReadDerivation {

type Typeclass[T] = BSONReader[_ <: BSONValue, T]

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = {
new BSONReader[BSONValue, T] {
override def read(bson: BSONValue): T = bson match {
case doc: BSONDocument =>
caseClass.construct { p =>
doc.getAsUnflattenedTry(p.label)(p.typeclass) match {
case Success(Some(v)) => v
case Success(None) => p.default.get // TODO better ex
case Failure(ex) => throw ex
}
}
case _ => throw new IllegalStateException("we only handle-case classes")
}
}
}

def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = {
new BSONReader[BSONValue, T] {
override def read(bson: BSONValue): T = {
bson match {
case doc: BSONDocument =>
val className = doc.getAs[String]("className").getOrElse(throw new IllegalStateException("'className' is required for sealed traits"))
val subtype = sealedTrait.subtypes.find(_.typeName.full == className).get
subtype.typeclass.asInstanceOf[BSONReader[BSONValue, T]].read(doc)
case _ => throw new IllegalStateException("we only handle-case classes")
}
}
}
}

}

object BSONWriteDerivation {

type Typeclass[T] = BSONWriter[T, _ <: BSONValue]

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = {
new BSONWriter[T, BSONValue]() {
override def write(t: T): BSONDocument = BSONDocument(caseClass.parameters.map(p => (p.label, p.typeclass.write(p.dereference(t)))))
}
}

def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = {
new BSONWriter[T, BSONValue]() {
override def write(t: T): BSONValue = sealedTrait.dispatch(t) { subType =>
subType.typeclass.writeTry(subType.cast(t)) match {
case Success(doc@BSONDocument(_)) => (doc ++ ("className" -> subType.typeName.full))
case Success(v) => v
case Failure(ex) => throw ex
}
}
}
}
}

object test {
import reactivemongo.bson.DefaultBSONHandlers._
val a0: BSONReader[BSONString, String] = DefaultBSONHandlers.BSONStringHandler
val a1: BSONReader[_, String] = a0

// val b: BSONReadDerivation.Typeclass[Seq[String]] = DefaultBSONHandlers.bsonArrayToCollectionReader[Seq, String]

}
16 changes: 16 additions & 0 deletions src/main/scala/magnolia/bson/derivation/handler/auto.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package magnolia.bson.derivation.handler

import scala.language.experimental.macros

object auto {

// type Typeclass[T] = BSONReader[_ <: BSONValue, T]
//
// def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] =
// BSONReadDerivation.combine(caseClass)
//
// def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] =
// BSONReadDerivation.dispatch(sealedTrait)
//
// implicit def magnoliaReader[T]: Typeclass[T] = macro Magnolia.gen[T]
}
29 changes: 29 additions & 0 deletions src/main/scala/magnolia/bson/derivation/handler/semiauto.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package magnolia.bson.derivation.handler

import magnolia.bson.{BSONReadDerivation, BSONWriteDerivation}
import magnolia.{CaseClass, Magnolia, SealedTrait}
import reactivemongo.bson.{BSONDocumentHandler, BSONHandler, BSONReader, BSONValue, BSONWriter}

import scala.language.experimental.macros

object semiauto {

type Typeclass[T] = BSONHandler[_ <: BSONValue, T]

val readDerivation = BSONReadDerivation
val writeDerivation = BSONWriteDerivation

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = BSONHandler.provided[BSONValue, T](
writeDerivation.combine(caseClass.asInstanceOf[CaseClass[writeDerivation.Typeclass,T]]).asInstanceOf[BSONWriter[T, BSONValue]],
readDerivation.combine(caseClass.asInstanceOf[CaseClass[readDerivation.Typeclass,T]]).asInstanceOf[BSONReader[BSONValue, T]]
)

def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = BSONHandler.provided[BSONValue, T](
writeDerivation.dispatch(sealedTrait.asInstanceOf[SealedTrait[writeDerivation.Typeclass,T]]).asInstanceOf[BSONWriter[T, BSONValue]],
readDerivation.dispatch(sealedTrait.asInstanceOf[SealedTrait[readDerivation.Typeclass,T]]).asInstanceOf[BSONReader[BSONValue, T]]
)

def deriveMagnoliaHandler[T]: BSONDocumentHandler[T] = macro Magnolia.gen[T]
}

//object semiauto extends semiauto
20 changes: 20 additions & 0 deletions src/main/scala/magnolia/bson/derivation/reader/auto.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package magnolia.bson.derivation.reader

import magnolia.bson.BSONReadDerivation
import magnolia.{CaseClass, Magnolia, SealedTrait}
import reactivemongo.bson.{BSONReader, BSONValue}

import scala.language.experimental.macros

object auto {

// type Typeclass[T] = BSONReader[_ <: BSONValue, T]
//
// def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] =
// BSONReadDerivation.combine(caseClass)
//
// def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] =
// BSONReadDerivation.dispatch(sealedTrait)
//
// implicit def magnoliaReader[T]: Typeclass[T] = macro Magnolia.gen[T]
}
25 changes: 25 additions & 0 deletions src/main/scala/magnolia/bson/derivation/reader/semiauto.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package magnolia.bson.derivation.reader

import magnolia.bson.BSONReadDerivation
import magnolia.{CaseClass, Magnolia, SealedTrait, debug}
import reactivemongo.bson.{BSONDocument, BSONDocumentReader, BSONReader, BSONValue, VariantBSONReader}

import scala.language.experimental.macros

object semiauto {

// type B <: BSONValue
private type Typeclass[T] = BSONReadDerivation.Typeclass[T]
//
// val d = new BSONReadDerivation[B] {}

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] =
BSONReadDerivation.combine(caseClass)

def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] =
BSONReadDerivation.dispatch(sealedTrait)

def deriveMagnoliaReader[T]: Typeclass[T] = macro Magnolia.gen[T]
}

//object semiauto extends semiauto
20 changes: 20 additions & 0 deletions src/main/scala/magnolia/bson/derivation/writer/auto.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package magnolia.bson.derivation.writer

import magnolia.bson.BSONWriteDerivation
import magnolia.{CaseClass, Magnolia, SealedTrait}
import reactivemongo.bson.{BSONValue, BSONWriter}

import scala.language.experimental.macros

object auto {
//
// type Typeclass[T] = BSONWriter[T, _ <: BSONValue]
//
// def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] =
// BSONWriteDerivation.combine(caseClass)
//
// def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] =
// BSONWriteDerivation.dispatch(sealedTrait)
//
// implicit def magnoliaWriter[T]: Typeclass[T] = macro Magnolia.gen[T]
}
23 changes: 23 additions & 0 deletions src/main/scala/magnolia/bson/derivation/writer/semiauto.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package magnolia.bson.derivation.writer

import magnolia.bson.BSONWriteDerivation
import magnolia.{CaseClass, Magnolia, SealedTrait}
import reactivemongo.bson.{BSONDocument, BSONValue, BSONWriter}

import scala.language.experimental.macros

object semiauto {

// type B <: BSONValue
private type Typeclass[T] = BSONWriteDerivation.Typeclass[T]
//
// val d = new BSONWriteDerivation[B] {}

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] =
BSONWriteDerivation.combine(caseClass)

def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] =
BSONWriteDerivation.dispatch(sealedTrait)

def deriveMagnoliaWriter[T]: Typeclass[T] = macro Magnolia.gen[T]
}
50 changes: 31 additions & 19 deletions src/test/scala/BSONDerivationTest.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import BSONDerivation.Typeclass
import reactivemongo.bson.{BSONArray, BSONDocument, BSONHandler, BSONReader, BSONValue}
import reactivemongo.bson.DefaultBSONHandlers._

object BSONDerivationTest {

implicit def SeqHandler[T](implicit handler: BSONHandler[_ <: BSONValue, T]): BSONHandler[BSONArray , Seq[T]] =
BSONHandler(
v => new BSONArrayCollectionReader[Seq, T]().read(v),
ts => BSONArray(ts.map(handler.write))
)
package magnolia.bson

import magnolia.debug
import reactivemongo.bson._

object examples {
case class Coordinates(lat: Double, long: Double)
case class City(name: String, location: Coordinates)

Expand All @@ -19,23 +12,42 @@ object BSONDerivationTest {
case object Taxi extends Transport

case class Trip(cities: Seq[City], transports: Seq[Transport])
object Trip {
import magnolia.bson.derivation.reader.semiauto._
import magnolia.bson.derivation.writer.semiauto._

implicit val transportReader = deriveMagnoliaReader[Transport]
implicit val transportWriter = deriveMagnoliaWriter[Transport]
implicit val coordinatesReader = deriveMagnoliaReader[Coordinates]
implicit val coordinatesWriter = deriveMagnoliaWriter[Coordinates]
implicit val cityReader = deriveMagnoliaReader[City]
implicit val cityWriter = deriveMagnoliaWriter[City]

implicit val tripReader: BSONReader[BSONValue, Trip] = deriveMagnoliaReader[Trip].asInstanceOf[BSONReader[BSONValue, Trip]]
implicit val tripWriter: BSONWriter[Trip, BSONValue] = deriveMagnoliaWriter[Trip].asInstanceOf[BSONWriter[Trip,BSONValue]]
}
}

private val cityA = City("Frauenfeld", Coordinates(1, 2))
private val cityB = City("Lisbon", Coordinates(3, 4))
private val trip =
object BSONDerivationTest {

def main(args: Array[String]): Unit = {
import examples._

val cityA = City("Frauenfeld", Coordinates(1, 2))
val cityB = City("Lisbon", Coordinates(3, 4))
val trip =
Trip(
cities = Seq(cityA, cityB),
transports = Seq(Flight("Swiss"), Taxi, Flight("Lufthansa"))
)

def main(args: Array[String]): Unit = {
implicit val transportWriter: Typeclass[Transport] = BSONDerivation.gen[Transport]
implicit val cityWriter: Typeclass[City] = BSONDerivation.gen[City]

val cs = BSONDerivation.gen[Trip].write(trip)
val t = BSONDerivation.gen[Trip].asInstanceOf[BSONHandler[BSONValue, Trip]].read(cs)
val cs = Trip.tripWriter.write(trip)
val t = Trip.tripReader.read(cs)

println(t == trip)
}

}


0 comments on commit c22eb9f

Please sign in to comment.