Skip to content

Instantly share code, notes, and snippets.

@guersam
Last active November 26, 2015 08:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save guersam/5d62815ee51bfa805cff to your computer and use it in GitHub Desktop.
Save guersam/5d62815ee51bfa805cff to your computer and use it in GitHub Desktop.
Generic `Serialize` derivation for scodec-msgpack
import scodec._
import scodec.bits.ByteVector
import scodec.msgpack._
import scala.collection.generic.{CanBuildFrom, IsTraversableOnce}
import scala.language.higherKinds
trait BasicMsgpackSerialize {
implicit def serializeBinary: Serialize[ByteVector] = Serialize.binary
implicit def serializeBool: Serialize[Boolean] = Serialize.bool
implicit def serializeInt: Serialize[Int] = Serialize.int
implicit def serializeDouble: Serialize[Double] = Serialize.double
implicit def serializeFloat: Serialize[Float] = Serialize.float
implicit def serializeLong: Serialize[Long] = Serialize.long
implicit def serializeString: Serialize[String] = Serialize.string
implicit def serializeOption[A](implicit S: Serialize[A]): Serialize[Option[A]] = new Serialize[Option[A]] {
override def pack(v: Option[A]): Attempt[MessagePack] =
v match {
case Some(x) => S.pack(x)
case None => Attempt.successful(MNil)
}
override def unpack(v: MessagePack): Attempt[Option[A]] =
v match {
case MNil => Attempt.successful(None)
case other => S.unpack(other).map(Some.apply)
}
}
implicit def serializeTraversableOnce[A0, C[_]](implicit
S: Serialize[A0],
is: IsTraversableOnce[C[A0]] { type A = A0 },
cbf: CanBuildFrom[Nothing, A0, C[A0]]
): Serialize[C[A0]] =
new Serialize[C[A0]] {
def pack(vs: C[A0]): Attempt[MessagePack] = {
val repr = is.conversion(vs)
val len = repr.size
repr.foldLeft(Attempt.successful(Vector.empty[MessagePack])){
case (acc, v) => acc.flatMap(a => S.pack(v).map(a :+ _))
}.map(vm =>
if(len <= 15) MFixArray(vm)
else if(len <= 65535) MArray16(vm)
else MArray32(vm)
)
}
private def sequence(vec: Vector[MessagePack]): Attempt[C[A0]] = {
val builder = cbf.apply()
builder.sizeHint(vec.length)
vec.foldLeft(Attempt.successful(builder)) {
case (acc, v) => acc.flatMap(b => S.unpack(v).map(b += _))
}.map(_.result)
}
def unpack(v: MessagePack): Attempt[C[A0]] = v match {
case MFixArray(n) => sequence(n)
case MArray16(n) => sequence(n)
case MArray32(n) => sequence(n)
case _ => Attempt.failure(Err("Fail to unpack: Array"))
}
}
implicit def serializeMap[A: Serialize, B: Serialize]: Serialize[Map[A, B]] = Serialize.map[A, B]
}
object BasicMsgpackSerialize extends BasicMsgpackSerialize
import scodec._
import scodec.msgpack._
import shapeless._
trait GenericMsgpackSerialize extends BasicMsgpackSerialize {
implicit val hnilSerialize: Serialize[HNil] = new Serialize[HNil] {
override def pack(v: HNil): Attempt[MessagePack] = Attempt.successful(MNil)
override def unpack(v: MessagePack): Attempt[HNil] =
v match {
case MNil => Attempt.successful(HNil)
case other => Attempt.failure(Err(s"MNil expected, but $other found."))
}
}
implicit def hconSerialize[H, T <: HList, R](implicit
hs: Lazy[Serialize[H]],
ts: Lazy[Serialize[T]]
): Serialize[H :: T] =
new Serialize[H :: T] {
override def pack(v: H :: T): Attempt[MessagePack] = {
val h :: t = v
for {
hp <- hs.value.pack(h)
tp <- ts.value.pack(t)
} yield MFixArray(Vector(hp, tp))
}
override def unpack(v: MessagePack): Attempt[H :: T] =
v match {
case MFixArray(Vector(hp, tp)) =>
for {
hv <- hs.value.unpack(hp)
tp <- ts.value.unpack(tp)
} yield hv :: tp
case other => Attempt.failure(Err(s"MFixArray with 2 elements expected, but $other found."))
}
}
implicit def productSerialize[A, Repr](implicit
hpg: HasProductGeneric[A],
gen: Generic.Aux[A, Repr],
sr: Lazy[Serialize[Repr]]
): Serialize[A] =
new Serialize[A] {
override def pack(v: A): Attempt[MessagePack] = sr.value.pack(gen to v)
override def unpack(v: MessagePack): Attempt[A] = sr.value.unpack(v).map(gen.from)
}
}
object GenericMsgpackSerialize extends GenericMsgpackSerialize
import scodec._
import scodec.bits._
import scodec.msgpack._
import scodec.msgpack.codecs.MessagePackCodec
import GenericMsgpackSerialize._
case class Address(zipCode: Int, street: String)
case class Person(name: String, age: Int, address: Option[Address])
// Same as scodec.msgpack.gen.
// Maybe good to move into `object Serialize`
implicit def serializeCodec[A](implicit s: Serialize[A]): Codec[A] =
new Codec[A] {
def encode(a: A): Attempt[BitVector] = s.pack(a).flatMap(MessagePackCodec.encode)
def decode(buffer: BitVector): Attempt[DecodeResult[A]] =
MessagePackCodec.decode(buffer).flatMap {
case DecodeResult(a, rest) => s.unpack(a).map(DecodeResult(_, rest))
}
def sizeBound = MessagePackCodec.sizeBound
}
val personCodec = Codec[Person]
val p = Person("Cosmic Owl", -100000, None)
assert(
(personCodec.encode(p) flatMap personCodec.decode) == Attempt.successful(DecodeResult(p, BitVector.empty))
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment