All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.evolutiongaming.kafka.journal.JsonCodec.scala Maven / Gradle / Ivy

The newest version!
package com.evolutiongaming.kafka.journal

import java.nio.charset.StandardCharsets

import cats.syntax.all._
import cats.{Applicative, ~>}
import com.evolutiongaming.catshelper.CatsHelper._
import com.evolutiongaming.catshelper.{ApplicativeThrowable, FromTry, MonadThrowable}
import com.evolutiongaming.jsonitertool.PlayJsonJsoniter
import com.evolutiongaming.kafka.journal.util.ScodecHelper._
import play.api.libs.json.{JsValue, Json}
import scodec.bits.ByteVector

import scala.util.Try

final case class JsonCodec[F[_]](
  encode: JsonCodec.Encode[F],
  decode: JsonCodec.Decode[F])

object JsonCodec {

  def summon[F[_]](implicit F: JsonCodec[F]): JsonCodec[F] = F


  def playJson[F[_]: Applicative: FromTry]: JsonCodec[F] = {
    JsonCodec(
      encode = Encode.playJson,
      decode = Decode.playJson)
  }


  def jsoniter[F[_]: Applicative: FromTry]: JsonCodec[F] = {
    JsonCodec(
      encode = Encode.jsoniter,
      decode = Decode.jsoniter)
  }


  def default[F[_]: ApplicativeThrowable: FromTry]: JsonCodec[F] = {
    JsonCodec(
      encode = Encode.jsoniter[F] fallbackTo Encode.playJson,
      decode = Decode.jsoniter[F] fallbackTo Decode.playJson)
  }


  implicit class JsonCodecOps[F[_]](val self: JsonCodec[F]) extends AnyVal {

    def mapK[G[_]](f: F ~> G): JsonCodec[G] = JsonCodec(self.encode.mapK(f), self.decode.mapK(f))
  }


  final case class Encode[F[_]](toBytes: ToBytes[F, JsValue])

  object Encode {

    def summon[F[_]](implicit F: Encode[F]): Encode[F] = F

    implicit def fromCodec[F[_]](implicit codec: JsonCodec[F]): Encode[F] = codec.encode


    def playJson[F[_]: Applicative: FromTry]: Encode[F] = {
      Encode { value =>
        FromTry[F].unsafe {
          val bytes = Json.toBytes(value)
          ByteVector.view(bytes)
        }
      }
    }


    def jsoniter[F[_]: Applicative: FromTry]: Encode[F] = {
      Encode { value =>
        FromTry[F].unsafe {
          val bytes = PlayJsonJsoniter.serialize(value)
          ByteVector.view(bytes)
        }
      }
    }


    implicit class EncodeOps[F[_]](val self: Encode[F]) extends AnyVal {

      def mapK[G[_]](f: F ~> G): Encode[G] = Encode(self.toBytes.mapK(f))

      def fallbackTo(encode: Encode[F])(implicit F: ApplicativeThrowable[F]): Encode[F] = {
        Encode { jsValue =>
          self
            .toBytes(jsValue)
            .handleErrorWith { e =>
              encode
                .toBytes(jsValue)
                .adaptErr { case _ => e }
            }
        }
      }

      def toStr(value: JsValue)(implicit F: MonadThrowable[F]): F[String] =
        for {
          bytes <- self.toBytes(value)
          str   <- bytes.decodeStr.liftTo[F]
        } yield str
    }
  }


  final case class Decode[F[_]](fromBytes: FromBytes[F, JsValue])

  object Decode {

    def summon[F[_]](implicit F: Decode[F]): Decode[F] = F

    implicit def fromCodec[F[_]](implicit codec: JsonCodec[F]): Decode[F] = codec.decode


    def playJson[F[_]: FromTry]: Decode[F] = {
      Decode { byteVector =>
        val bytes = byteVector.toArray
        Try { Json.parse(bytes) }.adapt(byteVector)
      }
    }


    def jsoniter[F[_]: FromTry]: Decode[F] = {
      Decode { byteVector =>
        PlayJsonJsoniter
          .deserialize(byteVector.toArray)
          .adapt(byteVector)
      }
    }


    implicit class DecodeOps[F[_]](val self: Decode[F]) extends AnyVal {

      def mapK[G[_]](f: F ~> G): Decode[G] = Decode(self.fromBytes.mapK(f))

      def fallbackTo(decode: Decode[F])(implicit F: ApplicativeThrowable[F]): Decode[F] = {
        Decode { bytes =>
          self
            .fromBytes(bytes)
            .handleErrorWith { e =>
              decode
                .fromBytes(bytes)
                .adaptErr { case _ => e }
            }
        }
      }

      def fromStr(str: String)(implicit F: MonadThrowable[F]): F[JsValue] =
        for {
          bytes <- ByteVector.encodeString(str)(StandardCharsets.UTF_8).liftTo[F]
          value <- self.fromBytes(bytes)
        } yield value
    }
  }


  private implicit class TryOps[A](val self: Try[A]) extends AnyVal {

    def adapt[F[_]: FromTry](byteVector: ByteVector): F[A] = {
      self
        .adaptErr { case e =>
          val str = byteVector
            .decodeStr
            .getOrElse { byteVector.toString() }
          JournalError(s"failed to parse $str: $e", e)
        }
        .fromTry[F]
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy