diff --git a/protobuf/js/src/main/scala/fluence/codec/Uint8ArrayCodecs.scala b/bits/js/src/main/scala/fluence/codec/bits/BitsUint8Codecs.scala similarity index 79% rename from protobuf/js/src/main/scala/fluence/codec/Uint8ArrayCodecs.scala rename to bits/js/src/main/scala/fluence/codec/bits/BitsUint8Codecs.scala index ed20768..e6873b4 100644 --- a/protobuf/js/src/main/scala/fluence/codec/Uint8ArrayCodecs.scala +++ b/bits/js/src/main/scala/fluence/codec/bits/BitsUint8Codecs.scala @@ -15,26 +15,18 @@ * along with this program. If not, see . */ -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) - ) } diff --git a/bits/src/main/scala/fluence/codec/bits/BitsCodecs.scala b/bits/src/main/scala/fluence/codec/bits/BitsCodecs.scala new file mode 100644 index 0000000..ffb6dbb --- /dev/null +++ b/bits/src/main/scala/fluence/codec/bits/BitsCodecs.scala @@ -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 . + */ + +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)) + ) + +} diff --git a/bits/src/test/scala/fluence/codec/bits/BitsCodecsSpec.scala b/bits/src/test/scala/fluence/codec/bits/BitsCodecsSpec.scala new file mode 100644 index 0000000..131b369 --- /dev/null +++ b/bits/src/test/scala/fluence/codec/bits/BitsCodecsSpec.scala @@ -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 . + */ + +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 + } + } +} diff --git a/circe/src/main/scala/fluence/codec/circe/CirceCodecs.scala b/circe/src/main/scala/fluence/codec/circe/CirceCodecs.scala new file mode 100644 index 0000000..580d8dd --- /dev/null +++ b/circe/src/main/scala/fluence/codec/circe/CirceCodecs.scala @@ -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 . + */ + +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))) + ) + +} diff --git a/core/js/src/main/scala/fluence/codec/Uint8Codecs.scala b/core/js/src/main/scala/fluence/codec/Uint8Codecs.scala new file mode 100644 index 0000000..0325eac --- /dev/null +++ b/core/js/src/main/scala/fluence/codec/Uint8Codecs.scala @@ -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 . + */ + +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) + ) +} diff --git a/core/src/main/scala/fluence/codec/Codec.scala b/core/src/main/scala/fluence/codec/Codec.scala index 377892c..04146e5 100644 --- a/core/src/main/scala/fluence/codec/Codec.scala +++ b/core/src/main/scala/fluence/codec/Codec.scala @@ -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]) } diff --git a/core/src/main/scala/fluence/codec/CodecError.scala b/core/src/main/scala/fluence/codec/CodecError.scala index e7a72ce..d26c4c0 100644 --- a/core/src/main/scala/fluence/codec/CodecError.scala +++ b/core/src/main/scala/fluence/codec/CodecError.scala @@ -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 diff --git a/core/src/main/scala/fluence/codec/MonadicalEitherArrow.scala b/core/src/main/scala/fluence/codec/MonadicalEitherArrow.scala index e786b6f..d3049bc 100644 --- a/core/src/main/scala/fluence/codec/MonadicalEitherArrow.scala +++ b/core/src/main/scala/fluence/codec/MonadicalEitherArrow.scala @@ -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. * diff --git a/core/src/main/scala/fluence/codec/PureCodec.scala b/core/src/main/scala/fluence/codec/PureCodec.scala index 3fd8506..7af6939 100644 --- a/core/src/main/scala/fluence/codec/PureCodec.scala +++ b/core/src/main/scala/fluence/codec/PureCodec.scala @@ -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) } diff --git a/core/src/test/scala/fluence/codec/PureCodecSpec.scala b/core/src/test/scala/fluence/codec/PureCodecSpec.scala index 47c45a8..f776bef 100644 --- a/core/src/test/scala/fluence/codec/PureCodecSpec.scala +++ b/core/src/test/scala/fluence/codec/PureCodecSpec.scala @@ -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 - - } } } diff --git a/protobuf/src/main/scala/fluence/codec/pb/ProtobufCodecs.scala b/protobuf/src/main/scala/fluence/codec/pb/ProtobufCodecs.scala index e3ccd2e..865c9e3 100644 --- a/protobuf/src/main/scala/fluence/codec/pb/ProtobufCodecs.scala +++ b/protobuf/src/main/scala/fluence/codec/pb/ProtobufCodecs.scala @@ -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,