Skip to content

Commit

Permalink
Atomic instances for ByteVector and BitVector
Browse files Browse the repository at this point in the history
See #36.
  • Loading branch information
durban committed Jan 18, 2017
1 parent 9e4a185 commit 4ead859
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 11 deletions.
89 changes: 80 additions & 9 deletions core/src/main/scala/io/sigs/seals/core/Atomic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,12 @@ object Atomic {

final override def binaryRepr(a: A): ByteVector = {
val arr = stringRepr(a).getBytes(UTF_8)
encodeLength(arr.length) ++ ByteVector.view(arr)
encodeLength32(arr.length) ++ ByteVector.view(arr)
}

final override def fromBinary(b: ByteVector): Either[Error, (A, ByteVector)] = {
for {
lenAndRest <- decodeLength(b)
lenAndRest <- decodeLength32(b)
(n, rest) = lenAndRest
bvs <- trySplit(rest, n.toLong)
(d, r) = bvs
Expand All @@ -142,12 +142,17 @@ object Atomic {
else Right(b.splitAt(n))
}

private final def encodeLength(i: Int): ByteVector = {
require(i >= 0, "negative length")
ByteVector.fromInt(i, 4)
private final def encodeLength32(n: Int): ByteVector = {
require(n >= 0, "negative length")
ByteVector.fromInt(n, 4)
}

private final def decodeLength(b: ByteVector): Either[Error, (Int, ByteVector)] = {
private final def encodeLength64(n: Long): ByteVector = {
require(n >= 0L, "negative length")
ByteVector.fromLong(n, 8)
}

private final def decodeLength32(b: ByteVector): Either[Error, (Int, ByteVector)] = {
for {
bvs <- trySplit(b, 4)
(l, r) = bvs
Expand All @@ -158,6 +163,17 @@ object Atomic {
} yield (n, r)
}

private final def decodeLength64(b: ByteVector): Either[Error, (Long, ByteVector)] = {
for {
bvs <- trySplit(b, 8)
(l, r) = bvs
n <- l.toLong(signed = true) match {
case n if n < 0 => Left(Error(s"negative encoded length: $n"))
case n => Right(n)
}
} yield (n, r)
}

def apply[A](implicit instance: Atomic[A]): Atomic[A] =
instance

Expand Down Expand Up @@ -407,12 +423,12 @@ object Atomic {

final override def binaryRepr(a: BigInt): ByteVector = {
val arr = a.underlying.toByteArray
encodeLength(arr.length) ++ ByteVector.view(arr)
encodeLength32(arr.length) ++ ByteVector.view(arr)
}

final override def fromBinary(b: ByteVector): Either[Error, (BigInt, ByteVector)] = {
for {
bvs <- decodeLength(b)
bvs <- decodeLength32(b)
(n, rest) = bvs
bvs <- if (n > 0) {
trySplit(rest, n.toLong)
Expand Down Expand Up @@ -551,6 +567,58 @@ object Atomic {
}
)

// Types from scodec-bits:

implicit val builtinByteVector: Atomic[ByteVector] =
SimpleByteVector

private object SimpleByteVector extends SimpleAtomic[ByteVector](
"ByteVector",
"98d436cc-3421-4bff-a575-28c1952b976a",
_.toBase64(Bases.Alphabets.Base64Url),
s => ByteVector.fromBase64Descriptive(s, Bases.Alphabets.Base64Url).leftMap(Error(_))
) {

override def binaryRepr(a: ByteVector): ByteVector =
encodeLength64(a.length) ++ a

override def fromBinary(b: ByteVector): Either[Error, (ByteVector, ByteVector)] = {
for {
lr <- decodeLength64(b)
(l, r) = lr
dr <- trySplit(r, l)
} yield dr
}
}

implicit val builtinBitVector: Atomic[BitVector] =
SimpleBitVector

private object SimpleBitVector extends SimpleAtomic[BitVector](
"BitVector",
"a533de88-6ebc-45c4-a40c-29588a1a1ef1",
_.toBin(Bases.Alphabets.Binary),
s => BitVector.fromBinDescriptive(s, Bases.Alphabets.Binary).leftMap(Error(_))
) {

override def binaryRepr(a: BitVector): ByteVector =
encodeLength64(a.length) ++ a.bytes

override def fromBinary(b: ByteVector): Either[Error, (BitVector, ByteVector)] = {
for {
lr <- decodeLength64(b)
(l, r) = lr
bytes = if ((l % 8L) > 0L) {
(l / 8L) + 1
} else {
l / 8L
}
dr <- trySplit(r, bytes)
(d, r) = dr
} yield (d.bits.take(l), r)
}
}

// Registry:

private[seals] val registry: Map[UUID, Model.Atom] = Map(
Expand All @@ -571,7 +639,10 @@ object Atomic {
entryOf[BigDecimal],
entryOf[MathContext],
entryOf[RoundingMode],
entryOf[UUID]
entryOf[UUID],
// scodec-bits:
entryOf[ByteVector],
entryOf[BitVector]
)

private def entryOf[A](implicit a: Atomic[A]): (UUID, Model.Atom) =
Expand Down
13 changes: 12 additions & 1 deletion laws/src/main/scala/io/sigs/seals/laws/ArbInstances.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import java.util.UUID

import shapeless._

import scodec.bits.ByteVector
import scodec.bits.{ ByteVector, BitVector }

import org.scalacheck.{ Arbitrary, Gen, Cogen }
import org.scalacheck.derive.Recursive
Expand Down Expand Up @@ -53,6 +53,17 @@ trait ArbInstances {
arbArr.arbitrary.map(ByteVector.view)
}

implicit def arbBitVector(implicit arbBytes: Arbitrary[ByteVector], arbByte: Arbitrary[Byte]): Arbitrary[BitVector] = Arbitrary {
Gen.oneOf(
arbBytes.arbitrary.map(_.bits),
for {
bytes <- arbBytes.arbitrary
byte <- arbByte.arbitrary
slice <- Gen.choose(1L, 7L)
} yield bytes.bits ++ BitVector.fromByte(byte, 8).take(slice)
)
}

implicit def arbEnvelope[A: Reified](implicit A: Arbitrary[A]): Arbitrary[Envelope[A]] = {
Arbitrary {
for {
Expand Down
18 changes: 18 additions & 0 deletions tests/src/test/scala/io/sigs/seals/tests/AtomicSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import org.scalatest.prop.GeneratorDrivenPropertyChecks

import cats.implicits._

import scodec.bits._

import laws.MyUUID
import laws.TestArbInstances.arbUuid
import laws.TestInstances.atomic._
Expand Down Expand Up @@ -131,4 +133,20 @@ class AtomicSpec extends BaseSpec with GeneratorDrivenPropertyChecks with Inside
case Left(Atomic.InvalidData(pat())) => // OK
}
}

"BitVector" in {
val a = Atomic[BitVector]
val bits = Vector(
BitVector.zero,
BitVector.one,
bin"11",
bin"11001",
bin"000001110000",
bin"1010101010111110101100011"
)
for (bv <- bits) {
a.fromString(a.stringRepr(bv)).getOrElse(fail) should === (bv)
a.fromBinary(a.binaryRepr(bv)).map(_._1).getOrElse(fail) should === (bv)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ package tests
import java.util.UUID
import java.math.{ MathContext, RoundingMode }

import scodec.bits.{ ByteVector, BitVector }

class BuiltinAtomSpec extends BaseSpec {

import Model.Atom.atom
Expand All @@ -43,7 +45,10 @@ class BuiltinAtomSpec extends BaseSpec {
atom[BigDecimal],
atom[MathContext],
atom[RoundingMode],
atom[UUID]
atom[UUID],
// scodec-bits:
atom[ByteVector],
atom[BitVector]
)
atoms.map(_.uuid).toSet should have size atoms.size.toLong
atoms.foreach { a =>
Expand Down
5 changes: 5 additions & 0 deletions tests/src/test/scala/io/sigs/seals/tests/LawsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import cats.laws.discipline.InvariantMonoidalTests
import cats.Eq
import cats.instances.all._

import scodec.bits._
import scodec.interop.cats._

import org.scalacheck.{ Arbitrary, Cogen }

import io.sigs.seals.laws._
Expand Down Expand Up @@ -64,6 +67,8 @@ class LawsSpec extends BaseLawsSpec {
checkAtomicLaws[MathContext]("MathContext")
checkAtomicLaws[RoundingMode]("RoundingMode")
checkAtomicLaws[UUID]("UUID")
checkAtomicLaws[ByteVector]("ByteVector")
checkAtomicLaws[BitVector]("BitVector")

checkAtomicLaws[Byte]("DerivedAtomicTester")(implicitly, implicitly, AtomicLaws.DerivedAtomicTester)
checkAtomicLaws[Int]("FallbackStringTester")(implicitly, implicitly, AtomicLaws.FallbackStringTester)
Expand Down

0 comments on commit 4ead859

Please sign in to comment.