Codec
is an opinionated library for cats
-friendly pure (reversible) conversions between types, with possible errors represented with Either
.
It's cross-built for Scala and Scala.js, and can also be used for other free-of-effect conversions to reach functional composability.
Often it's useful to do some validation alongside conversion, like in String => Int
.
However, this function throws when malformed input is given. Hence String => Either[Throwable, Int]
, being a total function, should fit better.
In this case, error is reflected in type system. It keeps things pure. We go further, forcing the error to be of type CodecError
, so that later it's easy to track where it comes from, especially in asynchronous environment.
This uni-direction type is called fluence.codec.PureCodec.Func
for a fixed CodecError
error type. Any other error type could be used by extending fluence.codec.MonadicalEitherArrow[Error]
.
Bidirection type A <=> B
is composed from two Func
s and is called Bijection
.
A type Func[A, B]
, being something like A => Either[E, B]
, is not very composable on it's own, so we implemented cats.arrow.ArrowChoice[Func]
for it. You may use cats.syntax.compose._
or anything like that to receive andThen
and other lawful functions.
Bijection[A, B]
is more complex type, so it has only Compose[Bijection]
typeclass. Finally you can do something like this:
import cats.syntax.compose._
import fluence.codec.PureCodec
val intToBytes: PureCodec[Int, Array[Byte]] = PureCodec[Int, String] andThen PureCodec[String, Array[Byte]]
Errors are handled in monad-like "fail-fast" fashion.
In general, functional types conversion could be lazy or eager, be performed in current thread or another. This choice should not affect the logic of conversion, as it's pure.
PureCodec
may use any monad to preform execution upon, retaining its nature. The most simple case is strict eager evaluation:
import cats.Id
val resEagerSync: Either[CodecError, Array[Byte]] = intToBytes.runF[Id](33)
You may use any monad, like Task
, Coeval
, Eval
, IO
...
codec-core
depends only on cats
. Each particular codec set is moved into separate module.
In case of complex algorithms, it's worthy to share codebase between platforms. We cross-compile all the codecs possible both to Scala and Scala.js.
// Bintray repo is used so far. Migration to Maven Central is planned
resolvers = Resolver.bintrayRepo("fluencelabs", "releases")
val codecV = "0.0.4"
libraryDependencies = Seq(
"one.fluence" %%% "codec-core" % codecV, // basic types
"one.fluence" %%% "codec-bits" % codecV, // scodec-bits conversions for ByteVector
"one.fluence" %%% "codec-circe" % codecV, // json handling with circe
"one.fluence" %%% "codec-protobuf" % codecV, // ByteString conversions for both scala and scala.js
"one.fluence" %% "codec-kryo" % codecV // typesafe kryo codecs, only for scala
)
import cats.syntax.compose._
import fluence.codec.PureCodec
import fluence.codec.circe.CirceCodecs._
import io.circe.{Decoder, Encoder, Json}
import scodec.bits.ByteVector
import fluence.codec.bits.BitsCodecs._
// Simple class
case class User(id: Int, name: String)
// Encode and decode with circe
implicit val encoder: Encoder[User] =
user ⇒ Json.obj("id" → Encoder.encodeInt(user.id), "name" → Encoder.encodeString(user.name))
implicit val decoder: Decoder[User] = cursor ⇒
for {
id ← cursor.downField("id").as[Int]
name ← cursor.downField("name").as[String]
} yield User(id, name)
// Get codec for encoder/decoder
implicit val userJson: PureCodec[User, Json] = circeJsonCodec(encoder, decoder)
// A trivial string to bytes codec; never use it in production!
implicit val stringCodec: PureCodec[String, Array[Byte]] =
PureCodec.liftB(_.getBytes, bs ⇒ new String(bs))
// Convert user to byte vector and vice versa
implicit val userJsonVec: PureCodec[User, ByteVector] =
PureCodec[User, Json] andThen
PureCodec[Json, String] andThen
PureCodec[String, Array[Byte]] andThen
PureCodec[Array[Byte], ByteVector]
// Try it with an instance
val user = User(234, "Hey Bob")
// unsafe() is to be used in tests only; it throws!
println(userJsonVec.direct.unsafe(user).toBase64)
// eyJpZCI6MjM0LCJuYW1lIjoiSGV5IEJvYiJ9
For more real-world examples, see Fluence.
connect[A, B, C]
to compose several Funcs or Bijectionssbt-tut
for docs- Implement more codecs
- Enhance
Func
api with shortcuts toEitherT
methods - Consider improving performance:
EitherT
is not so fast (at least yet)
Fluence is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License v3 (AGPLv3) as published by the Free Software Foundation.
Fluence includes some external modules that carry their own licensing.