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

com.evolutiongaming.kafka.journal.conversions.KafkaRead.scala Maven / Gradle / Ivy

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

import cats.syntax.all._
import cats.{Monad, ~>}
import com.evolutiongaming.catshelper._
import com.evolutiongaming.kafka.journal.PayloadAndType._
import com.evolutiongaming.kafka.journal._
import com.evolutiongaming.kafka.journal.util.Fail
import com.evolutiongaming.smetrics.MeasureDuration
import play.api.libs.json.JsValue

trait KafkaRead[F[_], A] {

  def apply(payloadAndType: PayloadAndType): F[Events[A]]
}

object KafkaRead {

  def summon[F[_], A](implicit kafkaRead: KafkaRead[F, A]): KafkaRead[F, A] = kafkaRead

  implicit def payloadKafkaRead[F[_]: MonadThrowable: FromAttempt: FromJsResult](implicit
    eventsFromBytes: FromBytes[F, Events[Payload]],
    payloadJsonFromBytes: FromBytes[F, PayloadJson[JsValue]]
  ): KafkaRead[F, Payload] = {

    payloadAndType: PayloadAndType => {

      def fromEventJsonPayload(jsonPayload: EventJsonPayloadAndType[JsValue]) = {
        def text = {
          FromJsResult[F]
            .apply { jsonPayload.payload.validate[String] }
            .map { str => Payload.text(str) }
        }

        jsonPayload.payloadType match {
          case PayloadType.Json => Payload.json(jsonPayload.payload).pure[F]
          case PayloadType.Text => text
        }
      }

      payloadAndType.payloadType match {
        case PayloadType.Binary =>
          withErrorAdapted(payloadAndType) {
            eventsFromBytes(payloadAndType.payload)
          }

        case PayloadType.Json =>
          val jsonKafkaRead = KafkaRead.readJson(payloadJsonFromBytes, fromEventJsonPayload)
          jsonKafkaRead(payloadAndType)
      }
    }
  }

  def readJson[F[_]: MonadThrowable, A, B](
    payloadJsonFromBytes: FromBytes[F, PayloadJson[A]],
    fromEventJsonPayload: EventJsonPayloadAndType[A] => F[B]
  ): KafkaRead[F, B] = {

    payloadAndType: PayloadAndType => {

      def events(payloadJson: PayloadJson[A]) = {
        payloadJson.events.traverse { event =>
          val payloadType = event.payloadType getOrElse PayloadType.Json
          val payload = event.payload
            .map(EventJsonPayloadAndType(_, payloadType))
            .traverse(fromEventJsonPayload)

          for {
            payload <- payload
          } yield {
            Event(
              seqNr = event.seqNr,
              tags = event.tags,
              payload = payload)
          }
        }
      }

      val payload = payloadAndType.payload
      val result = payloadAndType.payloadType match {
        case PayloadType.Json =>
          for {
            payloadJson <- payloadJsonFromBytes(payload)
            events      <- events(payloadJson)
          } yield {
            Events(
              events,
              payloadJson.metadata getOrElse PayloadMetadata.empty
            )
          }

        case other =>
          Fail.lift[F].fail[Events[B]](s"Only Json payload type supported, got: $other")
      }

      withErrorAdapted(payloadAndType)(result)
    }
  }

  private def withErrorAdapted[F[_]: ApplicativeThrowable, A](payloadAndType: PayloadAndType)(fa: F[A]): F[A] =
    fa.adaptErr { case e =>
      JournalError(s"KafkaRead failed for $payloadAndType: $e", e)
    }

  implicit class KafkaReadOps[F[_], A](val self: KafkaRead[F, A]) extends AnyVal {
    def withMetrics(
      metrics: KafkaReadMetrics[F]
    )(
      implicit F: Monad[F], measureDuration: MeasureDuration[F]
    ): KafkaRead[F, A] = {
      payloadAndType =>
        for {
          d <- MeasureDuration[F].start
          r <- self(payloadAndType)
          d <- d
          _ <- metrics(payloadAndType, d)
        } yield r
    }

    def mapK[G[_]](fg: F ~> G): KafkaRead[G, A] =
      payloadAndType => fg(self(payloadAndType))
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy