com.evolutiongaming.kafka.journal.util.PlayJsonHelper.scala Maven / Gradle / Ivy
The newest version!
package com.evolutiongaming.kafka.journal.util
import cats.syntax.all._
import cats.MonadError
import cats.data.{NonEmptyList => Nel}
import com.evolutiongaming.kafka.journal.JsonCodec
import com.evolutiongaming.scassandra.{DecodeByName, EncodeByName}
import play.api.libs.json._
import scala.annotation.tailrec
import scala.concurrent.duration._
import scala.util.Try
object PlayJsonHelper {
implicit def jsValueEncodeByName(implicit encode: JsonCodec.Encode[Try]): EncodeByName[JsValue] =
encodeByNameFromWrites
implicit def jsValueDecodeByName(implicit decode: JsonCodec.Decode[Try]): DecodeByName[JsValue] =
decodeByNameFromReads
implicit def jsValueOptEncodeByName(implicit encode: JsonCodec.Encode[Try]): EncodeByName[Option[JsValue]] =
EncodeByName.optEncodeByName
implicit def jsValueOptDecodeByName(implicit decode: JsonCodec.Decode[Try]): DecodeByName[Option[JsValue]] =
DecodeByName.optDecodeByName
implicit val jsResultMonadError: MonadError[JsResult, JsError] = new MonadError[JsResult, JsError] {
def raiseError[A](a: JsError) = a
def handleErrorWith[A](fa: JsResult[A])(f: JsError => JsResult[A]) = {
fa match {
case fa: JsSuccess[A] => fa
case fa: JsError => f(fa)
}
}
def pure[A](a: A) = JsSuccess(a)
def flatMap[A, B](fa: JsResult[A])(f: A => JsResult[B]) = fa.flatMap(f)
@tailrec
def tailRecM[A, B](a: A)(f: A => JsResult[Either[A, B]]): JsResult[B] = {
f(a) match {
case b: JsSuccess[Either[A, B]] => b.value match {
case Right(a) => JsSuccess(a)
case Left(b) => tailRecM(b)(f)
}
case b: JsError => b
}
}
}
implicit def nelReads[T](implicit reads: Reads[List[T]]): Reads[Nel[T]] = {
reads.mapResult {
case Nil => JsError("list is empty")
case head :: tail => JsSuccess(Nel(head, tail))
}
}
implicit def nelWrites[A](implicit writes: Writes[List[A]]): Writes[Nel[A]] = {
writes.contramap(_.toList)
}
implicit class ReadsOps[A](val self: Reads[A]) extends AnyVal {
final def mapResult[B](f: A => JsResult[B]): Reads[B] = (a: JsValue) => self.reads(a).flatMap(f)
}
def encodeByNameFromWrites[A](implicit writes: Writes[A], encode: JsonCodec.Encode[Try]): EncodeByName[A] = {
EncodeByName[String].contramap { a =>
val jsValue = writes.writes(a)
encode.toStr(jsValue).get
}
}
def decodeByNameFromReads[A](implicit reads: Reads[A], decode: JsonCodec.Decode[Try]): DecodeByName[A] = {
DecodeByName[String].map { str =>
val jsValue = decode.fromStr(str).get
// TODO not use `as`
jsValue.as(reads)
}
}
implicit val finiteDurationFormat: Format[FiniteDuration] = new Format[FiniteDuration] {
def reads(json: JsValue) = {
def fromString = for {
str <- json.validate[String]
} yield {
Try { Duration(str).asInstanceOf[FiniteDuration] }
.fold[JsResult[FiniteDuration]](
a => JsError(s"cannot parse FiniteDuration from $str: $a"),
a => JsSuccess(a))
}
def fromNumber = for {
a <- json.validate[Long]
} yield {
a.millis
}
fromString getOrElse fromNumber
}
def writes(a: FiniteDuration) = JsString(a.toString)
}
implicit class WritesOps[A](val self: Writes[A]) extends AnyVal {
def as[B <: A]: Writes[B] = self.contramap[B](identity)
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy