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

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

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

import cats.{Applicative, Monad}
import cats.syntax.all._
import com.evolutiongaming.kafka.journal.util.PlayJsonHelper._
import play.api.libs.json._


sealed abstract class ActionHeader extends Product {
  
  def origin: Option[Origin]

  def version: Option[Version]
}

object ActionHeader {

  val key: String = "journal.action"


  implicit val formatOptActionHeader: OFormat[Option[ActionHeader]] = {

    val appendFormat = {
      val format = Json.format[Append]
      val reads = format orElse new Reads[Append] {
        def reads(json: JsValue) = {

          def metadata = {
            (json \ "metadata").validate[JsObject] match {
              case JsSuccess(a, _) => a.validate[HeaderMetadata]
              case _: JsError      => HeaderMetadata.empty.pure[JsResult]
            }
          }

          for {
            range       <- (json \ "range").validate[SeqRange]
            origin      <- (json \ "origin").validateOpt[Origin]
            version     <- (json \ "version").validateOpt[Version]
            payloadType <- (json \ "payloadType").validate[PayloadType.BinaryOrJson]
            metadata    <- metadata
          } yield {
            Append(range, origin, version, payloadType, metadata)
          }
        }
      }
      OFormat(reads, format)
    }
    val deleteFormat = Json.format[Delete]
    val purgeFormat = Json.format[Purge]
    val readFormat = Json.format[Mark]

    new OFormat[Option[ActionHeader]] {

      def reads(json: JsValue) = {

        def read[A](name: String, reads: Reads[A]) = {
          (json \ name)
            .validate[JsObject]
            .asOpt
            .map { _.validate(reads) }
        }

        read("append", appendFormat)
          .orElse(read("mark", readFormat))
          .orElse(read("delete", deleteFormat))
          .orElse(read("purge", purgeFormat))
          .sequence
      }

      def writes(header: Option[ActionHeader]): JsObject = {

        def write[A](name: String, value: A, writes: Writes[A]) = {
          val json = writes.writes(value)
          Json.obj((name, json))
        }

        header.fold {
          Json.obj()
        } {
          case header: Append => write("append", header, appendFormat)
          case header: Mark   => write("mark", header, readFormat)
          case header: Delete => write("delete", header, deleteFormat)
          case header: Purge  => write("purge", header, purgeFormat)
        }
      }
    }
  }

  implicit val writesActionHeader: Writes[ActionHeader] = formatOptActionHeader.contramap { a: ActionHeader => a.some }


  implicit def toBytesActionHeader[F[_] : Applicative: JsonCodec.Encode]: ToBytes[F, ActionHeader] = ToBytes.fromWrites

  implicit def fromBytesOptActionHeader[F[_] : Monad : FromJsResult : JsonCodec.Decode]: FromBytes[F, Option[ActionHeader]] = FromBytes.fromReads


  sealed abstract class AppendOrDelete extends ActionHeader


  final case class Append(
    range: SeqRange,
    origin: Option[Origin],
    version: Option[Version],
    payloadType: PayloadType.BinaryOrJson,
    metadata: HeaderMetadata,
  ) extends AppendOrDelete


  final case class Delete(
    to: DeleteTo,
    origin: Option[Origin],
    version: Option[Version]
  ) extends AppendOrDelete


  final case class Purge(
    origin: Option[Origin],
    version: Option[Version]
  ) extends AppendOrDelete


  final case class Mark(
    id: String,
    origin: Option[Origin],
    version: Option[Version]
  ) extends ActionHeader
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy