From dd7b2023cdc3300ebb1ad0e67ee0c90ce429be14 Mon Sep 17 00:00:00 2001 From: Takeru Sato Date: Sun, 22 Aug 2021 02:56:12 +0900 Subject: [PATCH] feat: support timestamp type --- modules/core/src/main/scala/mess/Fmt.scala | 22 ++++++++--- .../src/main/scala/mess/codec/Decoder.scala | 6 +++ .../src/main/scala/mess/codec/Encoder.scala | 2 + .../scala/com/github/tkrs/CodecChecker.scala | 38 +++---------------- 4 files changed, 31 insertions(+), 37 deletions(-) diff --git a/modules/core/src/main/scala/mess/Fmt.scala b/modules/core/src/main/scala/mess/Fmt.scala index a8c64ac..b498424 100644 --- a/modules/core/src/main/scala/mess/Fmt.scala +++ b/modules/core/src/main/scala/mess/Fmt.scala @@ -3,6 +3,7 @@ package mess import java.math.BigInteger import java.nio.charset.Charset import java.nio.charset.StandardCharsets.UTF_8 +import java.time.Instant import mess.internal.ScalaVersionSpecifics._ import org.msgpack.core.MessagePack @@ -164,6 +165,13 @@ object Fmt { } } + final case class MTimestamp(timestamp: Instant) extends Fmt { + def isNil: Boolean = false + + def pack(buffer: MessagePacker): Unit = + buffer.packTimestamp(timestamp) + } + final case class MExtension(typ: Byte, size: Int, value: Array[Byte]) extends Fmt { def isNil: Boolean = false @@ -426,10 +434,13 @@ object Fmt { val size = buffer.unpackBinaryHeader() Fmt.MBin(buffer.readPayload(size)) case MF.EXT8 | MF.EXT16 | MF.EXT32 | MF.FIXEXT1 | MF.FIXEXT2 | MF.FIXEXT4 | MF.FIXEXT8 | MF.FIXEXT16 => - val ext = buffer.unpackExtensionTypeHeader() - val bytes = Array.ofDim[Byte](ext.getLength) - buffer.readPayload(bytes) - Fmt.MExtension(ext.getType, ext.getLength, bytes) + val ext = buffer.unpackExtensionTypeHeader() + if (ext.isTimestampType()) MTimestamp(buffer.unpackTimestamp()) + else { + val bytes = Array.ofDim[Byte](ext.getLength) + buffer.readPayload(bytes) + Fmt.MExtension(ext.getType, ext.getLength, bytes) + } case MF.NEVER_USED => sys.error("cannot unpack format: NEVER USED") } @@ -451,6 +462,7 @@ object Fmt { def fromLsit(value: List[Fmt]): Fmt = MArray(value.toVector) def fromSeq(value: Seq[Fmt]): Fmt = MArray(value.toVector) def fromValues(value: Fmt*): Fmt = MArray(value.toVector) - def fromBytes(x: Array[Byte]): Fmt = MBin(x) + def fromBytes(value: Array[Byte]): Fmt = MBin(value) + def fromInstant(value: Instant): Fmt = MTimestamp(value) def extension(typ: Byte, size: Int, bytes: Array[Byte]): Fmt = MExtension(typ, size, bytes) } diff --git a/modules/core/src/main/scala/mess/codec/Decoder.scala b/modules/core/src/main/scala/mess/codec/Decoder.scala index 8d559f0..0f26da2 100644 --- a/modules/core/src/main/scala/mess/codec/Decoder.scala +++ b/modules/core/src/main/scala/mess/codec/Decoder.scala @@ -1,6 +1,7 @@ package mess.codec import java.nio.charset.StandardCharsets.UTF_8 +import java.time.Instant import mess.Fmt import mess.internal.ScalaVersionSpecifics._ @@ -103,6 +104,11 @@ private[codec] trait Decoder1 { case m => Left(TypeMismatchError("String", m)) } + implicit val decodeTimestamp: Decoder[Instant] = { + case Fmt.MTimestamp(a) => Right(a) + case m => Left(TypeMismatchError("Timestamp", m)) + } + implicit def decodeSome[A](implicit decodeA: Decoder[A]): Decoder[Some[A]] = m => decodeA(m) match { diff --git a/modules/core/src/main/scala/mess/codec/Encoder.scala b/modules/core/src/main/scala/mess/codec/Encoder.scala index 969f31b..078fef9 100644 --- a/modules/core/src/main/scala/mess/codec/Encoder.scala +++ b/modules/core/src/main/scala/mess/codec/Encoder.scala @@ -4,6 +4,7 @@ import mess.Fmt import scala.annotation.tailrec import scala.collection.mutable +import java.time.Instant trait Encoder[A] extends Serializable { self => def apply(a: A): Fmt @@ -48,6 +49,7 @@ private[codec] trait Encoder1 { implicit final val encodeFloat: Encoder[Float] = Fmt.fromFloat(_) implicit final val encodeChar: Encoder[Char] = a => Fmt.fromString(a.toString) implicit final val encodeString: Encoder[String] = Fmt.fromString(_) + implicit final val encodeTimestamp: Encoder[Instant] = Fmt.fromInstant(_) implicit final def encodeSymbol[K <: Symbol]: Encoder[K] = a => Fmt.fromString(a.name) diff --git a/modules/core/src/test/scala/com/github/tkrs/CodecChecker.scala b/modules/core/src/test/scala/com/github/tkrs/CodecChecker.scala index 0c04b95..136aeea 100644 --- a/modules/core/src/test/scala/com/github/tkrs/CodecChecker.scala +++ b/modules/core/src/test/scala/com/github/tkrs/CodecChecker.scala @@ -5,10 +5,8 @@ import java.time.Instant import mess._ import mess.codec.Decoder import mess.codec.Encoder -import mess.codec.TypeMismatchError import mess.codec.semiauto._ import org.msgpack.core.MessagePack -import org.msgpack.core.MessagePack.Code import org.scalacheck.Arbitrary import org.scalacheck.Gen import org.scalacheck.Prop @@ -37,6 +35,11 @@ class CodecChecker extends MsgpackHelper { implicit val arbFix: Arbitrary[User[List]] = Arbitrary(genFix) } + implicit val arbInstant: Arbitrary[Instant] = Arbitrary(for { + seconds <- Gen.choose(0L, System.currentTimeMillis() / 1000) + nanos <- Gen.choose(0L, 999000000L) + } yield Instant.ofEpochSecond(seconds, nanos)) + def roundTrip[A: Arbitrary: Shrink](implicit encode: Encoder[A], decode: Decoder[A]) = Prop.forAll { a: A => val ast = encode(a) @@ -59,6 +62,7 @@ class CodecChecker extends MsgpackHelper { test("Double")(roundTrip[Double]) test("String")(roundTrip[String]) test("BigInt")(roundTrip[BigInt]) + test("Timestamp")(roundTrip[Instant]) test("Map[String, Int]")(roundTrip[Map[String, Int]]) test("Map[String, Long]")(roundTrip[Map[String, Long]]) test("Map[String, Float]")(roundTrip[Map[String, Float]]) @@ -117,36 +121,6 @@ class CodecChecker extends MsgpackHelper { test("Tuple22")(roundTrip[Tuple22[Int, Int, Long, String, BigInt, Double, Float, Long, Int, Byte, Short, Boolean, Int, String, List[String], Int, Int, Int, Int, Int, Int, Int]]) // format: on - implicit val arbInstant: Arbitrary[Instant] = Arbitrary(for { - seconds <- Gen.choose(0L, System.currentTimeMillis() / 1000) - nanos <- Gen.choose(0L, 999000000L) - } yield Instant.ofEpochSecond(seconds, nanos)) - - implicit val encodeInstantAsFluentdEventTime: Encoder[Instant] = a => { - val s = a.getEpochSecond - val n = a.getNano.toLong - - val f: (Long, Long) => Byte = (v, m) => (v >>> m).toByte - val fs: Long => Byte = f(s, _) - val fn: Long => Byte = f(n, _) - - val arr = Array(fs(24L), fs(16L), fs(8L), fs(0L), fn(24L), fn(16L), fn(8L), fn(0L)) - Fmt.extension(Code.EXT8, 8, arr) - } - - implicit val decodeInstantAsFluentdEventTime: Decoder[Instant] = { - case Fmt.MExtension(Code.EXT8, _, arr) => - val f: (Int, Long) => Long = (i, j) => (arr(i) & 0xff).toLong << j - - val seconds = f(0, 24L) | f(1, 16L) | f(2, 8L) | f(3, 0L) - val nanos = f(4, 24L) | f(5, 16L) | f(6, 8L) | f(7, 0L) - Right(Instant.ofEpochSecond(seconds, nanos)) - case a => - Left(TypeMismatchError("Instant", a)) - } - - test("Instant(Extension)")(roundTrip[Instant]) - case class Hoge(a: Option[Int]) object Hoge {