Skip to content

Commit

Permalink
add chameleon-http4s module
Browse files Browse the repository at this point in the history
provides http4s' EntityEncoder/EntityDecoder for supported Serializer and Deserializer
  • Loading branch information
cornerman committed Jun 10, 2024
1 parent 4e1cd51 commit dfa4f30
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 0 deletions.
11 changes: 11 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,14 @@ lazy val chameleon = crossProject(JSPlatform, JVMPlatform)
)
})
)

lazy val http4s = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Pure)
.dependsOn(chameleon)
.settings(commonSettings)
.settings(
name := "chameleon-http4s",
libraryDependencies ++=
Deps.http4s.value ::
Nil,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package chameleon.ext.http4s

import cats.effect.Concurrent
import cats.implicits._
import chameleon.{Deserializer, Serializer}
import org.http4s.headers.`Content-Type`
import org.http4s.{DecodeResult, EntityDecoder, EntityEncoder, Headers, MalformedMessageBodyFailure, Media, MediaType}

object JsonByteArrayCodec {
def jsonDecoderOf[F[_]: Concurrent, A](implicit deserializer: Deserializer[A, Array[Byte]]): EntityDecoder[F, A] =
EntityDecoder.decodeBy(MediaType.application.json) { media =>
EntityDecoder.collectBinary(media).flatMap { chunk =>
DecodeResult[F, A](Concurrent[F].pure(
deserializer.deserialize(chunk.toArray).leftMap(error => MalformedMessageBodyFailure("Invalid JSON", Some(error)))
))
}
}

def jsonEncoderOf[F[_], A](implicit serializer: Serializer[A, Array[Byte]]): EntityEncoder[F, A] =
EntityEncoder.encodeBy[F, A](Headers(`Content-Type`(MediaType.application.json))) {
EntityEncoder.byteArrayEncoder[F].contramap(serializer.serialize).toEntity(_)
}

case class json[A](value: A)

def jsonAs[A] = new JsonAsPartialApplied[A]
class JsonAsPartialApplied[A] {
def apply[F[_]: Concurrent](media: Media[F])(implicit deserializer: Deserializer[A, Array[Byte]]): F[A] = media.as[A](implicitly, jsonDecoderOf[F, A])
}

implicit def jsonEntityDecoderJson[F[_]: Concurrent, A](implicit deserializer: Deserializer[A, Array[Byte]]): EntityDecoder[F, json[A]] =
jsonDecoderOf[F, A].map(json(_))

implicit def jsonEntityEncoderJson[F[_], A](implicit serializer: Serializer[A, Array[Byte]]): EntityEncoder[F, json[A]] =
jsonEncoderOf[F, A].contramap(_.value)

object auto {
implicit def jsonEntityDecoder[F[_] : Concurrent, A](implicit deserializer: Deserializer[A, Array[Byte]]): EntityDecoder[F, A] = jsonDecoderOf[F, A]

implicit def jsonEntityEncoder[F[_], A](implicit serializer: Serializer[A, Array[Byte]]): EntityEncoder[F, A] = jsonEncoderOf[F, A]
}
}
42 changes: 42 additions & 0 deletions http4s/src/main/scala/chameleon/ext/http4s/JsonStringCodec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package chameleon.ext.http4s

import cats.effect.Concurrent
import cats.implicits._
import chameleon.{Deserializer, Serializer}
import org.http4s.headers.`Content-Type`
import org.http4s.{DecodeResult, EntityDecoder, EntityEncoder, Headers, MalformedMessageBodyFailure, Media, MediaType}

object JsonStringCodec {
def jsonDecoderOf[F[_] : Concurrent, A](implicit deserializer: Deserializer[A, String]): EntityDecoder[F, A] =
EntityDecoder.decodeBy(MediaType.application.json) { media =>
DecodeResult[F, A](
EntityDecoder.decodeText(media).map { json =>
deserializer.deserialize(json).leftMap(error => MalformedMessageBodyFailure("Invalid JSON", Some(error)))
}
)
}

def jsonEncoderOf[F[_], A](implicit serializer: Serializer[A, String]): EntityEncoder[F, A] =
EntityEncoder.encodeBy[F, A](Headers(`Content-Type`(MediaType.application.json))) {
EntityEncoder.stringEncoder[F].contramap(serializer.serialize).toEntity(_)
}

case class json[A](value: A)

def jsonAs[A] = new JsonAsPartialApplied[A]
class JsonAsPartialApplied[A] {
def apply[F[_]: Concurrent](media: Media[F])(implicit deserializer: Deserializer[A, String]): F[A] = media.as[A](implicitly, jsonDecoderOf[F, A])
}

implicit def jsonEntityDecoderJson[F[_]: Concurrent, A](implicit serializer: Deserializer[A, String]): EntityDecoder[F, json[A]] =
jsonDecoderOf[F, A].map(json(_))

implicit def jsonEntityEncoderJson[F[_], A](implicit serializer: Serializer[A, String]): EntityEncoder[F, json[A]] =
jsonEncoderOf[F, A].contramap(_.value)

object auto {
implicit def jsonEntityDecoder[F[_] : Concurrent, A](implicit deserializer: Deserializer[A, String]): EntityDecoder[F, A] = jsonDecoderOf[F, A]

implicit def jsonEntityEncoder[F[_], A](implicit serializer: Serializer[A, String]): EntityEncoder[F, A] = jsonEncoderOf[F, A]
}
}
2 changes: 2 additions & 0 deletions project/Deps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ object Deps {
val upickle = dep("com.lihaoyi" %%% "upickle" % "3.3.1")
val jsoniter = dep("com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % "2.30.1")
val zioJson = dep("dev.zio" %%% "zio-json" % "0.6.2")

val http4s = dep("org.http4s" %%% "http4s-core" % "0.23.24")
}

0 comments on commit dfa4f30

Please sign in to comment.