WIP: scodec and circe implementations of PureCodec's (#104)

* Introducing codec-bits

* Introducing codec-circe

* PureCodec in AesCrypt
This commit is contained in:
Dmitry Kurinskiy 2018-04-06 13:11:49 +03:00 committed by GitHub
parent db2bad1f7e
commit aba255ef2e
11 changed files with 193 additions and 74 deletions

View File

@ -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)
)
}

View 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))
)
}

View 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
}
}
}

View 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)))
)
}

View 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)
)
}

View File

@ -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])
}

View File

@ -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

View File

@ -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.
*

View File

@ -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)
}

View File

@ -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
}
}
}

View File

@ -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,