Last active
November 26, 2015 08:16
-
-
Save guersam/5d62815ee51bfa805cff to your computer and use it in GitHub Desktop.
Generic `Serialize` derivation for scodec-msgpack
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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