mirror of
https://github.com/fluencelabs/codec
synced 2025-04-24 14:22:14 +00:00
Codec in subproject (#5)
* codec in separate module * lisence headers * codec.direct and codec.inverse as Kleisli instances * test compilation error fixed * fork and not execute in parallel * better Codec; some dataset-grpc server implementations * license headers * compile bug fixed * kademlia's Key invariants checking * ContractsAllocatorApiServer implementation * node test fixed * better errors catching in KryoCodecs & disable logging in simulation tests
This commit is contained in:
commit
8d4983a636
65
core/src/main/scala/fluence/codec/Codec.scala
Normal file
65
core/src/main/scala/fluence/codec/Codec.scala
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.codec
|
||||
|
||||
import cats.data.Kleisli
|
||||
import cats.{ Applicative, FlatMap, Traverse }
|
||||
import cats.syntax.applicative._
|
||||
|
||||
import scala.language.{ higherKinds, implicitConversions }
|
||||
|
||||
/**
|
||||
* Base trait for serialize/deserialize objects.
|
||||
*
|
||||
* @tparam A The type of plain object representation
|
||||
* @tparam B The type of binary representation
|
||||
* @tparam F Encoding/decoding effect
|
||||
*/
|
||||
final case class Codec[F[_], A, B](encode: A ⇒ F[B], decode: B ⇒ F[A]) {
|
||||
self ⇒
|
||||
|
||||
val direct: Kleisli[F, A, B] = Kleisli(encode)
|
||||
|
||||
val inverse: Kleisli[F, B, A] = Kleisli(decode)
|
||||
|
||||
def andThen[C](other: Codec[F, B, C])(implicit F: FlatMap[F]): Codec[F, A, C] =
|
||||
Codec((self.direct andThen other.direct).run, (other.inverse andThen self.inverse).run)
|
||||
}
|
||||
|
||||
object Codec {
|
||||
implicit def identityCodec[F[_] : Applicative, T]: Codec[F, T, T] =
|
||||
Codec(_.pure[F], _.pure[F])
|
||||
|
||||
implicit def traverseCodec[F[_] : Applicative, G[_] : Traverse, O, B](implicit codec: Codec[F, O, B]): Codec[F, G[O], G[B]] =
|
||||
Codec[F, G[O], G[B]](Traverse[G].traverse[F, O, B](_)(codec.encode), Traverse[G].traverse[F, B, O](_)(codec.decode))
|
||||
|
||||
def codec[F[_], O, B](implicit codec: Codec[F, O, B]): Codec[F, O, B] = codec
|
||||
|
||||
/**
|
||||
* Constructs a Codec from pure encode/decode functions and an Applicative
|
||||
*
|
||||
* @param encodeFn Encode function that never fail
|
||||
* @param decodeFn Decode function that never fail
|
||||
* @tparam F Applicative effect
|
||||
* @tparam O Raw type
|
||||
* @tparam B Encoded type
|
||||
* @return New codec for O and B
|
||||
*/
|
||||
def pure[F[_] : Applicative, O, B](encodeFn: O ⇒ B, decodeFn: B ⇒ O): Codec[F, O, B] =
|
||||
Codec(encodeFn(_).pure[F], decodeFn(_).pure[F])
|
||||
}
|
105
kryo/src/main/scala/fluence/codec/kryo/KryoCodecs.scala
Normal file
105
kryo/src/main/scala/fluence/codec/kryo/KryoCodecs.scala
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.codec.kryo
|
||||
|
||||
import cats.MonadError
|
||||
import cats.syntax.flatMap._
|
||||
import com.twitter.chill.KryoPool
|
||||
import fluence.codec.Codec
|
||||
import shapeless._
|
||||
|
||||
import scala.language.higherKinds
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
/**
|
||||
* Wrapper for a KryoPool with a list of registered classes
|
||||
* @param pool Pre-configured KryoPool
|
||||
* @param F Applicative error
|
||||
* @tparam L List of classes registered with kryo
|
||||
* @tparam F Effect
|
||||
*/
|
||||
class KryoCodecs[F[_], L <: HList] private (pool: KryoPool)(implicit F: MonadError[F, Throwable]) {
|
||||
|
||||
/**
|
||||
* Returns a codec for any registered type
|
||||
* @param sel Shows the presence of type T within list L
|
||||
* @tparam T Object type
|
||||
* @return Freshly created Codec with Kryo inside
|
||||
*/
|
||||
implicit def codec[T](implicit sel: ops.hlist.Selector[L, T]): Codec[F, T, Array[Byte]] =
|
||||
Codec(
|
||||
obj ⇒ Option(obj) match {
|
||||
case Some(o) ⇒
|
||||
F.catchNonFatal(Option(pool.toBytesWithClass(o))).flatMap {
|
||||
case Some(v) ⇒ F.pure(v)
|
||||
case None ⇒ F.raiseError(new NullPointerException("Obj is encoded into null"))
|
||||
}
|
||||
case None ⇒
|
||||
F.raiseError[Array[Byte]](new NullPointerException("Obj is null, encoding is impossible"))
|
||||
}, binary ⇒ F.catchNonFatal(pool.fromBytes(binary).asInstanceOf[T]))
|
||||
}
|
||||
|
||||
object KryoCodecs {
|
||||
|
||||
/**
|
||||
* Builder for Kryo codecs
|
||||
* @param klasses Classes to register with Kryo
|
||||
* @tparam L List of registered classes
|
||||
*/
|
||||
class Builder[L <: HList] private[KryoCodecs] (klasses: Seq[Class[_]]) {
|
||||
/**
|
||||
* Register a new case class T to Kryo
|
||||
* @tparam T Type to add
|
||||
* @tparam S Generic representation of T
|
||||
* @param gen Generic representation of case type T
|
||||
* @param sa Presence of all types of S inside L
|
||||
* @return Extended builder
|
||||
*/
|
||||
def addCase[T, S <: HList](klass: Class[T])(implicit gen: Generic.Aux[T, S], sa: ops.hlist.SelectAll[L, S]): Builder[T :: L] =
|
||||
new Builder[T :: L](klasses :+ klass)
|
||||
|
||||
/**
|
||||
* Register a primitive type T to Kryo
|
||||
* @tparam T Type to add
|
||||
* @return Extended builder
|
||||
*/
|
||||
def add[T : ClassTag]: Builder[T :: L] =
|
||||
new Builder[T :: L](klasses :+ implicitly[ClassTag[T]].runtimeClass)
|
||||
|
||||
/**
|
||||
* Build a new instance of KryoCodecs with the given poolSize and F effect
|
||||
* @param poolSize Kryo pool size
|
||||
* @param F ApplicativeError for catching serialization errors
|
||||
* @tparam F Effect type
|
||||
* @return Configured instance of KryoCodecs
|
||||
*/
|
||||
def build[F[_]](poolSize: Int = Runtime.getRuntime.availableProcessors)(implicit F: MonadError[F, Throwable]): KryoCodecs[F, L] =
|
||||
new KryoCodecs[F, L](
|
||||
KryoPool.withByteArrayOutputStream(
|
||||
poolSize,
|
||||
KryoFactory(klasses, registrationRequired = true) // registrationRequired should never be needed, as codec derivation is typesafe
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a fresh builder
|
||||
*/
|
||||
def apply(): Builder[Array[Byte] :: Long :: String :: HNil] =
|
||||
new Builder[HNil](Vector.empty).add[String].add[Long].add[Array[Byte]]
|
||||
}
|
40
kryo/src/main/scala/fluence/codec/kryo/KryoFactory.scala
Normal file
40
kryo/src/main/scala/fluence/codec/kryo/KryoFactory.scala
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Fluence Labs Limited
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.codec.kryo
|
||||
|
||||
import com.twitter.chill.{ AllScalaRegistrar, KryoBase, KryoInstantiator }
|
||||
import org.objenesis.strategy.StdInstantiatorStrategy
|
||||
|
||||
/**
|
||||
* This Instantiator enable compulsory class registration, registers all java and scala main classes.
|
||||
* This class required for [[com.twitter.chill.KryoPool]].
|
||||
* @param classesToReg additional classes for registration
|
||||
* @param registrationRequired if true, an exception is thrown when an unregistered class is encountered.
|
||||
*/
|
||||
private[kryo] case class KryoFactory(classesToReg: Seq[Class[_]], registrationRequired: Boolean) extends KryoInstantiator {
|
||||
|
||||
override def newKryo(): KryoBase = {
|
||||
val kryo = new KryoBase()
|
||||
kryo.setRegistrationRequired(registrationRequired)
|
||||
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy())
|
||||
new AllScalaRegistrar()(kryo)
|
||||
// in future will be able to create specific fast serializer for each class
|
||||
kryo.registerClasses(classesToReg)
|
||||
kryo
|
||||
}
|
||||
}
|
55
kryo/src/test/scala/fluence/codec/kryo/KryoCodecsSpec.scala
Normal file
55
kryo/src/test/scala/fluence/codec/kryo/KryoCodecsSpec.scala
Normal file
@ -0,0 +1,55 @@
|
||||
package fluence.codec.kryo
|
||||
|
||||
import cats.instances.try_._
|
||||
import org.scalatest.{ Matchers, WordSpec }
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
class KryoCodecsSpec extends WordSpec with Matchers {
|
||||
|
||||
val testBlob = Array("3".getBytes(), "4".getBytes(), "5".getBytes())
|
||||
val testClass = TestClass("one", 2, testBlob)
|
||||
|
||||
private val testCodecs =
|
||||
KryoCodecs()
|
||||
.add[Array[Array[Byte]]]
|
||||
.addCase(classOf[TestClass]).build[Try]()
|
||||
|
||||
"encode and decode" should {
|
||||
"be inverse functions" when {
|
||||
"object defined" in {
|
||||
|
||||
val codec = testCodecs.codec[TestClass]
|
||||
|
||||
val result = codec.encode(testClass).flatMap(codec.decode).get
|
||||
|
||||
result.str shouldBe "one"
|
||||
result.num shouldBe 2
|
||||
result.blob should contain theSameElementsAs testBlob
|
||||
}
|
||||
|
||||
"object is null" in {
|
||||
val codec = testCodecs.codec[TestClass]
|
||||
val result = codec.encode(null).flatMap(codec.decode)
|
||||
result.isFailure shouldBe true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"encode" should {
|
||||
"don't write full class name to binary representation" when {
|
||||
"class registered" in {
|
||||
//val codec = KryoCodec[TestClass](Seq(classOf[TestClass], classOf[Array[Byte]], classOf[Array[Array[Byte]]]), registerRequired = true)
|
||||
val codec = testCodecs.codec[TestClass]
|
||||
val encoded = codec.encode(testClass).map(new String(_)).get
|
||||
val reasonableMaxSize = 20 // bytes
|
||||
encoded should not contain "TestClass"
|
||||
encoded.length should be < reasonableMaxSize
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
case class TestClass(str: String, num: Long, blob: Array[Array[Byte]])
|
||||
|
Loading…
x
Reference in New Issue
Block a user