Skip to content
This repository has been archived by the owner on Jul 20, 2022. It is now read-only.

Commit

Permalink
Merge pull request #5 from tkrs/overhaul-generic-codec
Browse files Browse the repository at this point in the history
Overhaul generic codec
  • Loading branch information
tkrs authored Dec 20, 2019
2 parents 1114a19 + e1ce929 commit 0ab5aff
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 102 deletions.
46 changes: 4 additions & 42 deletions core/src/main/scala/agni/Binder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,15 @@ package agni

import com.datastax.oss.driver.api.core.ProtocolVersion
import com.datastax.oss.driver.api.core.cql.BoundStatement
import shapeless.labelled.FieldType
import shapeless.{ ::, HList, HNil, LabelledGeneric, Lazy, Witness }

trait Binder[A] {
def apply(bound: BoundStatement, version: ProtocolVersion, a: A): Either[Throwable, BoundStatement]
}

object Binder extends LowPriorityBinder with TupleBinder {
object Binder extends TupleBinder {

def apply[A](implicit A: Binder[A]): Binder[A] = A

implicit val bindHnil: Binder[HNil] = new Binder[HNil] {
override def apply(bound: BoundStatement, version: ProtocolVersion, a: HNil): Either[Throwable, BoundStatement] = Right(bound)
}

implicit def bindLabelledHList[K <: Symbol, H, T <: HList](
implicit
K: Witness.Aux[K],
H: RowSerializer[H],
T: Binder[T]
): Binder[FieldType[K, H] :: T] =
new Binder[FieldType[K, H] :: T] {
override def apply(bound: BoundStatement, version: ProtocolVersion, xs: FieldType[K, H] :: T): Either[Throwable, BoundStatement] =
xs match {
case h :: t => for {
b <- H(bound, K.value.name, h, version)
r <- T(b, version, t)
} yield r
}
}

implicit def bindSingle[A](implicit A: RowSerializer[A]): Binder[A] = new Binder[A] {
override def apply(bound: BoundStatement, version: ProtocolVersion, a: A): Either[Throwable, BoundStatement] =
A.apply(bound, 0, a, version)
}
}

trait LowPriorityBinder {

implicit def bindCaseClass[A, R <: HList](
implicit
gen: LabelledGeneric.Aux[A, R],
bind: Lazy[Binder[R]]
): Binder[A] =
new Binder[A] {
override def apply(bound: BoundStatement, version: ProtocolVersion, a: A): Either[Throwable, BoundStatement] =
bind.value.apply(bound, version, gen.to(a))
}

}
implicit def bindSingle[A](implicit serializeA: RowSerializer[A]): Binder[A] =
(bound, version, a) => serializeA(bound, 0, a, version)
}
50 changes: 3 additions & 47 deletions core/src/main/scala/agni/RowDecoder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,15 @@ package agni

import com.datastax.oss.driver.api.core.ProtocolVersion
import com.datastax.oss.driver.api.core.cql.Row
import shapeless.labelled._
import shapeless.{ ::, HList, HNil, LabelledGeneric, Lazy, Witness }

trait RowDecoder[A] {
def apply(row: Row, version: ProtocolVersion): Either[Throwable, A]
}

object RowDecoder extends LowPriorityRowDecoder with TupleRowDecoder {
object RowDecoder extends TupleRowDecoder {

def apply[A](implicit A: RowDecoder[A]): RowDecoder[A] = A

def unsafeGet[A: RowDecoder](f: Row => Either[Throwable, A]): RowDecoder[A] =
new RowDecoder[A] {
def apply(s: Row, version: ProtocolVersion): Either[Throwable, A] = f(s)
}

implicit val decodeHNil: RowDecoder[HNil] =
new RowDecoder[HNil] {
def apply(s: Row, version: ProtocolVersion): Either[Throwable, HNil] =
Right(HNil)
}

implicit def decodeLabelledHList[K <: Symbol, H, T <: HList](
implicit
K: Witness.Aux[K],
H: RowDeserializer[H],
T: RowDecoder[T]
): RowDecoder[FieldType[K, H] :: T] =
new RowDecoder[FieldType[K, H] :: T] {
def apply(row: Row, version: ProtocolVersion): Either[Throwable, FieldType[K, H] :: T] = for {
h <- H(row, K.value.name, version)
t <- T(row, version)
} yield field[K](h) :: t
}

implicit def decodeSingleColumn[A](
implicit
A: RowDeserializer[A]
): RowDecoder[A] = new RowDecoder[A] {
def apply(row: Row, version: ProtocolVersion): Either[Throwable, A] =
A.apply(row, 0, version)
}
}

trait LowPriorityRowDecoder {

implicit def decodeCaseClass[A, R <: HList](
implicit
gen: LabelledGeneric.Aux[A, R],
decode: Lazy[RowDecoder[R]]
): RowDecoder[A] =
new RowDecoder[A] {
def apply(s: Row, version: ProtocolVersion): Either[Throwable, A] =
decode.value(s, version) map (gen from)
}
implicit def decodeSingleColumn[A](implicit deserializeA: RowDeserializer[A]): RowDecoder[A] =
(row, version) => deserializeA(row, 0, version)
}
30 changes: 30 additions & 0 deletions core/src/main/scala/agni/generic/DerivedBinder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package agni.generic

import agni.{ Binder, RowSerializer }
import shapeless.labelled.FieldType
import shapeless.{ ::, HList, HNil, LabelledGeneric, Lazy, Witness }

trait DerivedBinder[A] extends Binder[A]

object DerivedBinder extends DerivedBinder1

trait DerivedBinder1 {

implicit val bindHnil: DerivedBinder[HNil] = (bound, _, _) => Right(bound)

implicit def bindLabelledHList[K <: Symbol, H, T <: HList](
implicit
K: Witness.Aux[K],
H: RowSerializer[H],
T: DerivedBinder[T]
): DerivedBinder[FieldType[K, H] :: T] =
(bound, version, xs) => H(bound, K.value.name, xs.head, version).flatMap(T(_, version, xs.tail))

implicit def bindCaseClass[A, R <: HList](
implicit
gen: LabelledGeneric.Aux[A, R],
bind: Lazy[DerivedBinder[R]]
): DerivedBinder[A] =
(bound, version, a) =>
bind.value(bound, version, gen.to(a))
}
33 changes: 33 additions & 0 deletions core/src/main/scala/agni/generic/DerivedRowDecoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package agni.generic

import agni.{ RowDecoder, RowDeserializer }
import shapeless.labelled._
import shapeless.{ ::, HList, HNil, LabelledGeneric, Lazy, Witness }

trait DerivedRowDecoder[A] extends RowDecoder[A]

object DerivedRowDecoder extends DerivedRowDecoder1

trait DerivedRowDecoder1 {

implicit val decodeHNil: DerivedRowDecoder[HNil] =
(_, version) => Right(HNil)

implicit def decodeLabelledHList[K <: Symbol, H, T <: HList](
implicit
K: Witness.Aux[K],
H: RowDeserializer[H],
T: DerivedRowDecoder[T]
): DerivedRowDecoder[FieldType[K, H] :: T] =
(row, version) => for {
h <- H(row, K.value.name, version)
t <- T(row, version)
} yield field[K](h) :: t

implicit def decodeCaseClass[A, R <: HList](
implicit
gen: LabelledGeneric.Aux[A, R],
decode: Lazy[DerivedRowDecoder[R]]
): DerivedRowDecoder[A] =
(row, version) => decode.value(row, version) map (gen from)
}
9 changes: 9 additions & 0 deletions core/src/main/scala/agni/generic/auto.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package agni.generic

import agni.{ Binder, RowDecoder }
import shapeless.Lazy

object auto {
implicit def autoDerivedBinder[A](implicit A: Lazy[DerivedBinder[A]]): Binder[A] = A.value
implicit def autoDerivedRowDecoder[A](implicit A: Lazy[DerivedRowDecoder[A]]): RowDecoder[A] = A.value
}
9 changes: 9 additions & 0 deletions core/src/main/scala/agni/generic/semiauto.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package agni.generic

import agni.{ Binder, RowDecoder }
import shapeless.Lazy

object semiauto {
def derivedBinder[A](implicit A: Lazy[DerivedBinder[A]]): Binder[A] = A.value
def derivedRowDecoder[A](implicit A: Lazy[DerivedRowDecoder[A]]): RowDecoder[A] = A.value
}
6 changes: 1 addition & 5 deletions core/src/test/scala/agni/BinderSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,11 @@ import org.scalatest.Assertion
import org.scalatestplus.mockito.MockitoSugar

class BinderSpec extends TypedSuite with MockitoSugar {
import TypedSuite._

def checkType[A: Binder]: Assertion = {
assertCompiles("Binder.apply[A]")
}

test("Binder[Named]")(checkType[Named])
test("Binder[IDV]")(checkType[IDV])

test("Binder[T1]")(checkType[T1])
test("Binder[T2]")(checkType[T2])
test("Binder[T3]")(checkType[T3])
Expand Down Expand Up @@ -60,4 +56,4 @@ class BinderSpec extends TypedSuite with MockitoSugar {

assert(e === s)
}
}
}
4 changes: 0 additions & 4 deletions core/src/test/scala/agni/RowDecoderSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,11 @@ package agni
import org.scalatest.Assertion

class RowDecoderSpec extends TypedSuite {
import TypedSuite._

def checkType[A: RowDecoder]: Assertion = {
assertCompiles("RowDecoder.apply[A]")
}

test("RowDecoder[Named]")(checkType[Named])
test("RowDecoder[IDV]")(checkType[IDV])

test("RowDecoder[T1]")(checkType[T1])
test("RowDecoder[T2]")(checkType[T2])
test("RowDecoder[T3]")(checkType[T3])
Expand Down
17 changes: 17 additions & 0 deletions core/src/test/scala/agni/generic/DerivedBinderSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package agni.generic

import agni.{ Binder, TypedSuite }
import org.scalatest.Assertion
import org.scalatestplus.mockito.MockitoSugar

class DerivedBinderSpec extends TypedSuite with MockitoSugar {
import auto._
import TypedSuite._

def checkType[A: Binder]: Assertion = {
assertCompiles("Binder.apply[A]")
}

test("Binder[Named]")(checkType[Named])
test("Binder[IDV]")(checkType[IDV])
}
16 changes: 16 additions & 0 deletions core/src/test/scala/agni/generic/DerivedRowDecoderSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package agni.generic

import agni.{ RowDecoder, TypedSuite }
import org.scalatest.Assertion

class RowDecoderSpec extends TypedSuite {
import TypedSuite._
import auto._

def checkType[A: RowDecoder]: Assertion = {
assertCompiles("RowDecoder.apply[A]")
}

test("RowDecoder[Named]")(checkType[Named])
test("RowDecoder[IDV]")(checkType[IDV])
}
12 changes: 8 additions & 4 deletions examples/src/main/scala/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ object Main extends IOApp with Matchers {
_ <- Cql.prepareAsync[IO](session, createTableQuery) >>= (p => Cql.executeAsync[IO](session, p.bind()))
} yield ()

private[this] val decode: RowDecoder[Author] = RowDecoder[Author]

def action(session: CqlSession): IO[Stream[Author]] = for {
_ <- remake(session)

Expand All @@ -37,8 +35,7 @@ object Main extends IOApp with Matchers {
v = session.getContext.getProtocolVersion

xs <- Cql.prepareAsync[IO](session, selectUserQuery).flatTap(a => IO(println(a.getQuery))) >>=
(p => Cql.getRows[IO](session, p.bind())) >>=
(rows => rows.traverse(row => IO.fromEither(decode(row, v))))
(p => Cql.getRowsAs[IO, Author](session, p.bind()))
} yield xs

def insertUser(session: CqlSession, p: PreparedStatement, a: Author): IO[Unit] =
Expand Down Expand Up @@ -117,3 +114,10 @@ final case class Author(
gender: String,
works: Map[String, Int]
)

object Author {
import agni.generic.semiauto._

implicit val bind: Binder[Author] = derivedBinder[Author]
implicit val decode: RowDecoder[Author] = derivedRowDecoder[Author]
}

0 comments on commit 0ab5aff

Please sign in to comment.