mirror of
https://github.com/fluencelabs/codec
synced 2025-04-24 14:22:14 +00:00
WIP: scodec and circe implementations of PureCodec's (#104)
* Introducing codec-bits * Introducing codec-circe * PureCodec in AesCrypt
This commit is contained in:
parent
db2bad1f7e
commit
aba255ef2e
@ -15,26 +15,18 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package fluence.codec
|
||||
package fluence.codec.bits
|
||||
|
||||
import fluence.codec.PureCodec
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.language.higherKinds
|
||||
|
||||
import scala.scalajs.js.typedarray.Uint8Array
|
||||
import scala.scalajs.js.JSConverters._
|
||||
|
||||
object Uint8ArrayCodecs {
|
||||
|
||||
object BitsUint8Codecs {
|
||||
implicit val byteVectorUint8Array: PureCodec[Uint8Array, ByteVector] =
|
||||
PureCodec.liftB(
|
||||
uint8 ⇒ ByteVector(uint8.toArray.map(_.toByte)),
|
||||
vec ⇒ new Uint8Array(vec.toArray.map(_.toShort).toJSArray)
|
||||
)
|
||||
|
||||
implicit val byteArrayUint8Array: PureCodec[Uint8Array, Array[Byte]] =
|
||||
PureCodec.liftB(
|
||||
uint8 ⇒ uint8.toArray.map(_.toByte),
|
||||
arr ⇒ new Uint8Array(arr.toJSArray)
|
||||
)
|
||||
}
|
46
bits/src/main/scala/fluence/codec/bits/BitsCodecs.scala
Normal file
46
bits/src/main/scala/fluence/codec/bits/BitsCodecs.scala
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.bits
|
||||
|
||||
import fluence.codec.{CodecError, PureCodec}
|
||||
import scodec.bits.{Bases, ByteVector}
|
||||
|
||||
/**
|
||||
* Implicit codecs for scodec data structures.
|
||||
*/
|
||||
object BitsCodecs {
|
||||
import PureCodec.{liftB, liftEitherB}
|
||||
|
||||
implicit val byteArrayToVector: PureCodec[Array[Byte], ByteVector] =
|
||||
liftB(ByteVector.apply, _.toArray)
|
||||
|
||||
// Notice the use of default Base64 alphabet
|
||||
implicit val base64ToVector: PureCodec[String, ByteVector] =
|
||||
base64AlphabetToVector(Bases.Alphabets.Base64)
|
||||
|
||||
def base64AlphabetToVector(alphabet: Bases.Base64Alphabet): PureCodec[String, ByteVector] =
|
||||
liftEitherB(
|
||||
str ⇒
|
||||
ByteVector
|
||||
.fromBase64Descriptive(str, alphabet)
|
||||
.left
|
||||
.map(CodecError(_)),
|
||||
vec ⇒ Right(vec.toBase64(alphabet))
|
||||
)
|
||||
|
||||
}
|
47
bits/src/test/scala/fluence/codec/bits/BitsCodecsSpec.scala
Normal file
47
bits/src/test/scala/fluence/codec/bits/BitsCodecsSpec.scala
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.bits
|
||||
|
||||
import cats.Id
|
||||
import cats.syntax.compose._
|
||||
import org.scalatest.prop.Checkers
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
import scodec.bits.ByteVector
|
||||
import BitsCodecs._
|
||||
import fluence.codec.PureCodec
|
||||
|
||||
class BitsCodecsSpec extends WordSpec with Matchers with Checkers {
|
||||
"BitsCodecs" should {
|
||||
"convert base64 strings to byte vectors and vice versa" in {
|
||||
|
||||
val arrCodec = implicitly[PureCodec[Array[Byte], ByteVector]]
|
||||
val b64Codec = implicitly[PureCodec[ByteVector, String]]
|
||||
|
||||
check { (bytes: List[Byte]) ⇒
|
||||
(arrCodec andThen arrCodec.swap).direct.apply[Id](bytes.toArray).value.map(_.toList).contains(bytes) &&
|
||||
(arrCodec andThen b64Codec andThen b64Codec.swap andThen arrCodec.swap).direct
|
||||
.apply[Id](bytes.toArray)
|
||||
.value
|
||||
.map(_.toList)
|
||||
.contains(bytes)
|
||||
}
|
||||
|
||||
b64Codec.inverse[Id]("wrong input!").value.isLeft shouldBe true
|
||||
}
|
||||
}
|
||||
}
|
37
circe/src/main/scala/fluence/codec/circe/CirceCodecs.scala
Normal file
37
circe/src/main/scala/fluence/codec/circe/CirceCodecs.scala
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.circe
|
||||
|
||||
import fluence.codec.{CodecError, PureCodec}
|
||||
import io.circe._
|
||||
|
||||
object CirceCodecs {
|
||||
|
||||
implicit def circeJsonCodec[T](encoder: Encoder[T], decoder: Decoder[T]): PureCodec[T, Json] =
|
||||
PureCodec.liftEitherB[T, Json](
|
||||
t ⇒ Right(encoder.apply(t)),
|
||||
json ⇒ decoder.decodeJson(json).left.map(f ⇒ CodecError("Cannot decode json value", causedBy = Some(f)))
|
||||
)
|
||||
|
||||
implicit val circeJsonParseCodec: PureCodec[Json, String] =
|
||||
PureCodec.liftEitherB(
|
||||
json ⇒ Right(json.noSpaces),
|
||||
str ⇒ parser.parse(str).left.map(f ⇒ CodecError("Cannot parse json string", causedBy = Some(f)))
|
||||
)
|
||||
|
||||
}
|
30
core/js/src/main/scala/fluence/codec/Uint8Codecs.scala
Normal file
30
core/js/src/main/scala/fluence/codec/Uint8Codecs.scala
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 scala.scalajs.js.JSConverters._
|
||||
import scala.scalajs.js.typedarray.Uint8Array
|
||||
|
||||
object Uint8Codecs {
|
||||
|
||||
implicit val byteArrayUint8Array: PureCodec[Uint8Array, Array[Byte]] =
|
||||
PureCodec.liftB(
|
||||
uint8 ⇒ uint8.toArray.map(_.toByte),
|
||||
arr ⇒ new Uint8Array(arr.toJSArray)
|
||||
)
|
||||
}
|
@ -18,9 +18,8 @@
|
||||
package fluence.codec
|
||||
|
||||
import cats.data.Kleisli
|
||||
import cats.{Applicative, ApplicativeError, FlatMap, Traverse}
|
||||
import cats.{Applicative, FlatMap, Traverse}
|
||||
import cats.syntax.applicative._
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.language.{higherKinds, implicitConversions}
|
||||
|
||||
@ -31,6 +30,10 @@ import scala.language.{higherKinds, implicitConversions}
|
||||
* @tparam B The type of binary representation
|
||||
* @tparam F Encoding/decoding effect
|
||||
*/
|
||||
@deprecated(
|
||||
"Codec is planned for removing soon, as it's impure and not properly tested. Use PureCodec instead.",
|
||||
"6.4.2018"
|
||||
)
|
||||
final case class Codec[F[_], A, B](encode: A ⇒ F[B], decode: B ⇒ F[A]) {
|
||||
self ⇒
|
||||
|
||||
@ -47,6 +50,10 @@ final case class Codec[F[_], A, B](encode: A ⇒ F[B], decode: B ⇒ F[A]) {
|
||||
def swap: Codec[F, B, A] = Codec(decode, encode)
|
||||
}
|
||||
|
||||
@deprecated(
|
||||
"Codec is planned for removing soon, as it's impure and not properly tested. Use PureCodec instead.",
|
||||
"6.4.2018"
|
||||
)
|
||||
object Codec {
|
||||
implicit def identityCodec[F[_]: Applicative, T]: Codec[F, T, T] =
|
||||
Codec(_.pure[F], _.pure[F])
|
||||
@ -65,21 +72,10 @@ object Codec {
|
||||
implicit def swap[F[_], A, B](implicit cod: Codec[F, A, B]): Codec[F, B, A] =
|
||||
Codec[F, B, A](cod.decode, cod.encode)
|
||||
|
||||
implicit def byteVectorArray[F[_]: Applicative]: Codec[F, Array[Byte], ByteVector] =
|
||||
pure(ByteVector.apply, _.toArray)
|
||||
|
||||
// TODO: descriptive error
|
||||
implicit def byteVectorB64[F[_]](implicit F: ApplicativeError[F, Throwable]): Codec[F, String, ByteVector] =
|
||||
Codec(
|
||||
str ⇒
|
||||
ByteVector
|
||||
.fromBase64(str)
|
||||
.fold[F[ByteVector]](
|
||||
F.raiseError(new IllegalArgumentException(s"Given string is not valid b64: $str"))
|
||||
)(_.pure[F]),
|
||||
_.toBase64.pure[F]
|
||||
@deprecated(
|
||||
"Codec is planned for removing soon, as it's impure and not properly tested. Use PureCodec instead.",
|
||||
"6.4.2018"
|
||||
)
|
||||
|
||||
def codec[F[_], O, B](implicit codec: Codec[F, O, B]): Codec[F, O, B] = codec
|
||||
|
||||
/**
|
||||
@ -92,6 +88,10 @@ object Codec {
|
||||
* @tparam B Encoded type
|
||||
* @return New codec for O and B
|
||||
*/
|
||||
@deprecated(
|
||||
"Codec is planned for removing soon, as it's impure and not properly tested. Use PureCodec instead.",
|
||||
"6.4.2018"
|
||||
)
|
||||
def pure[F[_]: Applicative, O, B](encodeFn: O ⇒ B, decodeFn: B ⇒ O): Codec[F, O, B] =
|
||||
Codec(encodeFn(_).pure[F], decodeFn(_).pure[F])
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import scala.util.control.NoStackTrace
|
||||
* it's returned in the left side of Either(T) by PureCodec transformations.
|
||||
*
|
||||
* @param message Error message
|
||||
* @param causedBy Error cause for wrapped exceptions
|
||||
*/
|
||||
case class CodecError(message: String, causedBy: Option[Throwable] = None) extends NoStackTrace {
|
||||
override def getMessage: String = message
|
||||
|
@ -27,7 +27,7 @@ import scala.language.higherKinds
|
||||
import scala.util.Try
|
||||
|
||||
/**
|
||||
* MonadicalEitherFunc wraps Func and Bijection with a fixed E error type.
|
||||
* MonadicalEitherArrow wraps Func and Bijection with a fixed E error type.
|
||||
*
|
||||
* @tparam E Error type
|
||||
*/
|
||||
@ -108,6 +108,10 @@ abstract class MonadicalEitherArrow[E <: Throwable] {
|
||||
*/
|
||||
lazy val swap: Bijection[B, A] = Bijection(inverse, direct)
|
||||
|
||||
@deprecated(
|
||||
"You should keep codec Pure until running direct or inverse on it: there's no reason to bind effect into Codec",
|
||||
"6.4.2018"
|
||||
)
|
||||
def toCodec[F[_]](implicit F: MonadError[F, Throwable]): Codec[F, A, B] =
|
||||
Codec(direct.runF[F], inverse.runF[F])
|
||||
}
|
||||
@ -144,6 +148,11 @@ abstract class MonadicalEitherArrow[E <: Throwable] {
|
||||
Traverse[G].traverse[EitherT[F, E, ?], A, B](input)(f.apply[F](_))
|
||||
}
|
||||
|
||||
/**
|
||||
* Bijection Summoner -- useful for making a composition of bijections.
|
||||
*/
|
||||
def apply[A, B](implicit bijection: Bijection[A, B]): Bijection[A, B] = bijection
|
||||
|
||||
/**
|
||||
* Lifts a pure function into Func context.
|
||||
*
|
||||
|
@ -17,26 +17,11 @@
|
||||
|
||||
package fluence.codec
|
||||
|
||||
import scodec.bits.{Bases, ByteVector}
|
||||
|
||||
/**
|
||||
* PureCodec default values.
|
||||
*/
|
||||
object PureCodec extends MonadicalEitherArrow[CodecError] {
|
||||
|
||||
implicit val byteArrayToVector: PureCodec[Array[Byte], ByteVector] =
|
||||
liftB(ByteVector.apply, _.toArray)
|
||||
|
||||
implicit val base64ToVector: PureCodec[String, ByteVector] =
|
||||
liftEitherB(
|
||||
str ⇒
|
||||
ByteVector
|
||||
.fromBase64Descriptive(str, Bases.Alphabets.Base64)
|
||||
.left
|
||||
.map(CodecError(_)),
|
||||
vec ⇒ Right(vec.toBase64(Bases.Alphabets.Base64))
|
||||
)
|
||||
|
||||
/**
|
||||
* Summons an implicit codec.
|
||||
*/
|
||||
@ -45,12 +30,12 @@ object PureCodec extends MonadicalEitherArrow[CodecError] {
|
||||
/**
|
||||
* Shortcut to build a PureCodec.Bijection with two Func's
|
||||
*/
|
||||
def apply[A, B](direct: Func[A, B], inverse: Func[B, A]): Bijection[A, B] =
|
||||
def build[A, B](direct: Func[A, B], inverse: Func[B, A]): Bijection[A, B] =
|
||||
Bijection(direct, inverse)
|
||||
|
||||
/**
|
||||
* Shortcut to build a PureCodec.Bijection with two pure functions
|
||||
*/
|
||||
def apply[A, B](direct: A ⇒ B, inverse: B ⇒ A): Bijection[A, B] =
|
||||
def build[A, B](direct: A ⇒ B, inverse: B ⇒ A): Bijection[A, B] =
|
||||
liftB(direct, inverse)
|
||||
}
|
||||
|
@ -17,12 +17,9 @@
|
||||
|
||||
package fluence.codec
|
||||
|
||||
import cats.Id
|
||||
import cats.instances.try_._
|
||||
import cats.syntax.compose._
|
||||
import org.scalatest.prop.Checkers
|
||||
import org.scalatest.{Matchers, WordSpec}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
@ -37,23 +34,5 @@ class PureCodecSpec extends WordSpec with Matchers with Checkers {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"convert base64 strings to byte vectors and vice versa" in {
|
||||
|
||||
val arrCodec = implicitly[PureCodec[Array[Byte], ByteVector]]
|
||||
val b64Codec = implicitly[PureCodec[ByteVector, String]]
|
||||
|
||||
check { (bytes: List[Byte]) ⇒
|
||||
(arrCodec andThen arrCodec.swap).direct.apply[Id](bytes.toArray).value.map(_.toList).contains(bytes) &&
|
||||
(arrCodec andThen b64Codec andThen b64Codec.swap andThen arrCodec.swap).direct
|
||||
.apply[Id](bytes.toArray)
|
||||
.value
|
||||
.map(_.toList)
|
||||
.contains(bytes)
|
||||
}
|
||||
|
||||
b64Codec.inverse[Id]("wrong input!").value.isLeft shouldBe true
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,18 +19,11 @@ package fluence.codec.pb
|
||||
|
||||
import com.google.protobuf.ByteString
|
||||
import fluence.codec.PureCodec
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.language.higherKinds
|
||||
|
||||
object ProtobufCodecs {
|
||||
|
||||
implicit val byteVectorByteString: PureCodec[ByteString, ByteVector] =
|
||||
PureCodec.liftB(
|
||||
str ⇒ ByteVector(str.toByteArray),
|
||||
vec ⇒ ByteString.copyFrom(vec.toArray)
|
||||
)
|
||||
|
||||
implicit val byteArrayByteString: PureCodec[ByteString, Array[Byte]] =
|
||||
PureCodec.liftB(
|
||||
str ⇒ str.toByteArray,
|
||||
|
Loading…
x
Reference in New Issue
Block a user