Skip to content
Open
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
6 changes: 5 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ organization := "io.tabmo"
scalaVersion := "2.11.8"

scalacOptions ++= Seq(
"-Ypartial-unification", // enable fix for SI-2712
"-Yliteral-types", // enable SIP-23 implementation
"-deprecation", // Warn when deprecated API are used
"-feature", // Warn for usages of features that should be importer explicitly
"-unchecked", // Warn when generated code depends on assumptions
Expand All @@ -24,7 +26,7 @@ libraryDependencies ++= Seq(
"com.aerospike" % "aerospike-client" % "3.2.5",
"ch.qos.logback" % "logback-classic" % "1.1.3",
"org.scalatest" % "scalatest_2.11" % "2.2.1" % "test",
"io.github.jto" %% "validation-core" % "2.0"
"com.chuusai" %% "shapeless" % "2.3.2"
)

parallelExecution in Test := false
Expand All @@ -37,5 +39,7 @@ licenses += ("Apache-2.0", url("http://opensource.org/licenses/Apache-2.0"))

bintrayOrganization := Some("tabmo")

scalaOrganization := "org.typelevel"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu bosses chez typelevel ? :D


// Exclude logback file
mappings in (Compile, packageBin) ~= { _.filter(!_._1.getName.endsWith(".xml")) }
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=0.13.11
sbt.version=0.13.13
96 changes: 96 additions & 0 deletions src/main/scala/io/tabmo/aerospike/validation/AsDecoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package io.tabmo.aerospike.validation

import jto.validation.aerospike._

trait AsDecoder[A] {
self =>
def decode[V <: AsValue](v: V): Result[A]

def map[B](f: A => B): AsDecoder[B] = new AsDecoder[B] {
override def decode[V <: AsValue](v: V): Result[B] = self.decode(v).map(f)
}
}

object Result {
def apply[A](a: A): Result[A] = Done(a)
}

sealed abstract class Result[+A] {
def map[B](f: A => B): Result[B]

def flatMap[B](f: A => Result[B]): Result[B]
}
case class Done[A](a: A) extends Result[A] {
override def map[B](f: (A) => B): Result[B] = Done(f(a))

override def flatMap[B](f: (A) => Result[B]): Result[B] = f(a)
}
case object Failed extends Result[Nothing] {
override def map[B](f: (Nothing) => B): Result[B] = Failed

override def flatMap[B](f: (Nothing) => Result[B]): Result[B] = Failed
}

object AsDecoder {
import shapeless._
import shapeless.labelled._

def instance[A](f: AsValue => Result[A]): AsDecoder[A] = new AsDecoder[A] {
override def decode[V <: AsValue](v: V): Result[A] = f(v)
}

implicit val longDecoder: AsDecoder[Long] = instance {
case AsLong(v) => Done(v)
case _ => Failed
}

implicit val stringDecoder: AsDecoder[String] = instance {
case AsString(v) => Done(v)
case _ => Failed
}

implicit def listDecoder[A](implicit ev: AsDecoder[A]): AsDecoder[List[A]] = instance {
case AsArray(seq) => seq.foldRight(Result(List[A]())){ case (e, acc) =>
acc match {
case Done(s) => ev.decode(e).map(_ :: s)
case Failed => Failed
}
}
case _ => Failed
}

implicit def optionDecoder[A](implicit ev: AsDecoder[A]): AsDecoder[Option[A]] = instance { e =>
ev.decode(e) match {
case Done(x) => Done(Some(x))
case Failed => Done(None)
}
}

implicit val hnilDecoder: AsDecoder[HNil] = instance(_ => Done(HNil))

implicit def hlistDecoder[K <: Symbol, H, T <: HList](
implicit witness: Witness.Aux[K],
hDecoder: Lazy[AsDecoder[H]],
tDecoder: Lazy[AsDecoder[T]]
): AsDecoder[FieldType[K, H] :: T] = instance {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

T'étais chaud !! GG

case o@AsObject(kv) => {
val fieldName = witness.value.name
val headField = hDecoder.value.decode(kv.getOrElse(fieldName, AsNull))
val tailFields = tDecoder.value.decode(o)
(headField, tailFields) match {
case (Done(h), Done(t)) => Done(field[K](h) :: t)
case _ => Failed
}
}
case _ => Failed
}

implicit def objectDecoder[A, Repr <: HList](
implicit gen: LabelledGeneric.Aux[A, Repr],
hlistDecoder: AsDecoder[Repr]
): AsDecoder[A] = instance { v =>
hlistDecoder.decode(v).map(gen.from)
}

def apply[A](implicit ev: AsDecoder[A]): AsDecoder[A] = ev
}
58 changes: 58 additions & 0 deletions src/main/scala/io/tabmo/aerospike/validation/AsEncoder.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.tabmo.aerospike.validation

import jto.validation.aerospike._

trait AsEncoder[A] {
self =>

def encode(a: A): AsValue

def contramap[B](f: B => A): AsEncoder[B] = new AsEncoder[B] {
override def encode(b: B): AsValue = self.encode(f(b))
}
}

object AsEncoder {
import shapeless._
import shapeless.labelled._
import shapeless.ops.hlist.IsHCons

def instance[A](f: A => AsValue) = new AsEncoder[A] {
override def encode(a: A): AsValue = f(a)
}

implicit val longEncoder: AsEncoder[Long] = instance(AsLong)

implicit val stringEncoder: AsEncoder[String] = instance(AsString)

implicit def listEncoder[A](implicit ev: AsEncoder[A]): AsEncoder[List[A]] = instance { list =>
AsArray(list.map(ev.encode).toIndexedSeq)
}

implicit def optionEncoder[A](implicit ev: AsEncoder[A]): AsEncoder[Option[A]] = instance {
case Some(a) => ev.encode(a)
case None => AsNull
}

implicit val hnilEncoder: AsEncoder[HNil] = instance(_ => AsObject())

implicit def hlistEncoder[K <: Symbol, H, T <: shapeless.HList](
implicit witness: Witness.Aux[K],
isHCons: IsHCons.Aux[H :: T, H, T],
hEncoder: Lazy[AsEncoder[H]],
tEncoder: Lazy[AsEncoder[T]]
): AsEncoder[FieldType[K, H] :: T] = instance { o =>
val head = AsObject(witness.value.name, hEncoder.value.encode(isHCons.head(o)))
val tail = tEncoder.value.encode(isHCons.tail(o))
head ++ tail.asInstanceOf[AsObject]
}

implicit def objectEncoder[A, Repr <: HList](
implicit gen: LabelledGeneric.Aux[A, Repr],
hlistEncoder: AsEncoder[Repr]
): AsEncoder[A] = instance { o =>
hlistEncoder.encode(gen.to(o))
}

def apply[A](implicit ev: AsEncoder[A]): AsEncoder[A] = ev
}
31 changes: 24 additions & 7 deletions src/main/scala/io/tabmo/aerospike/validation/AsValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,36 @@ package aerospike
import scala.collection.JavaConversions._
import scala.collection.JavaConverters._

import com.aerospike.client.Bin
import io.tabmo.aerospike.data.AerospikeRecord

sealed trait AsValue
sealed trait AsValue {
def asObject = this.asInstanceOf[AsObject]
}

object AsValue {

def apply(value: AnyRef): AsValue = {
private def buildAST(value: AnyRef): AsValue = {
if (value == null) AsNull
else {
value match {
case _: java.util.Map[_, _] => AsObject(value.asInstanceOf[java.util.Map[String, AnyRef]].asScala.mapValues(AsValue.apply).toMap)
case _: java.util.List[_] => AsArray(value.asInstanceOf[java.util.List[AnyRef]].map(AsValue.apply).toIndexedSeq)
case _: java.util.Map[_, _] => AsObject(value.asInstanceOf[java.util.Map[String, AnyRef]].asScala.mapValues(buildAST).toMap)
case _: java.util.List[_] => AsArray(value.asInstanceOf[java.util.List[AnyRef]].map(buildAST).toIndexedSeq)
case l: java.lang.Long => AsLong(l)
case s: String => AsString(s)
case _ => throw new IllegalStateException()
}
}
}

def unpackAST(value: AsValue): Object = value match {
case AsObject(map) => map.mapValues(unpackAST).asJava
case AsArray(seq) => seq.map(unpackAST).toList.asJava
case AsLong(l) => new java.lang.Long(l)
case AsString(s) => s
}

def apply(record: AerospikeRecord): AsValue = {
this.apply(mapAsJavaMap(record.bins))
buildAST(mapAsJavaMap(record.bins))
}

def obj(fields: (String, AsValue)*): AsObject = AsObject(fields.toMap)
Expand All @@ -33,7 +42,15 @@ object AsValue {
def string(v: String): AsString = AsString(v)
}

case class AsObject(kv: Map[String, AsValue]) extends AsValue
case class AsObject(kv: Map[String, AsValue] = Map()) extends AsValue {
def ++(obj: AsObject): AsObject = AsObject(kv ++ obj.kv)

def toSeqBins: Seq[Bin] = kv.toList.map { case (k, v) => new Bin(k, AsValue.unpackAST(v)) }
}

object AsObject {
def apply[V <: AsValue](key: String, value: V): AsObject = AsObject(Map(key -> value))
}

case class AsArray(array: IndexedSeq[AsValue]) extends AsValue

Expand Down
100 changes: 0 additions & 100 deletions src/main/scala/io/tabmo/aerospike/validation/Rules.scala

This file was deleted.

Loading