Skip to content

Commit

Permalink
Update HttpContentCodec.scala
Browse files Browse the repository at this point in the history
  • Loading branch information
varshith257 authored Aug 31, 2024
1 parent 1eeff37 commit b523a81
Showing 1 changed file with 71 additions and 74 deletions.
145 changes: 71 additions & 74 deletions zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ import zio._
import zio.stream.ZPipeline

import zio.schema.codec._
import zio.schema.{DeriveSchema, Schema}
import zio.schema.{ DeriveSchema, Schema }

import zio.http.Header.Accept.MediaTypeWithQFactor
import zio.http._
import zio.http.codec.internal.TextBinaryCodec
import zio.http.internal.HeaderOps
import zio.http.template._

sealed trait HttpContentCodec[A](
) { self =>
sealed trait HttpContentCodec[A] { self =>

def choices: ListMap[MediaType, BinaryCodecWithSchema[A]]

Expand All @@ -35,7 +34,7 @@ sealed trait HttpContentCodec[A](
request.body.asChunk.flatMap { bytes =>
ZIO.fromEither(codec.codec(config).decode(bytes))
}
case None =>
case None =>
ZIO.fail(throw new IllegalArgumentException(s"No codec found for content type $contentType"))
}
}
Expand All @@ -50,15 +49,15 @@ sealed trait HttpContentCodec[A](
response.body.asChunk.flatMap { bytes =>
ZIO.fromEither(codec.codec(config).decode(bytes))
}
case None =>
case None =>
ZIO.fail(throw new IllegalArgumentException(s"No codec found for content type $contentType"))
}
}

def decodeResponse(response: Response): Task[A] =
CodecConfig.codecRef.getWith(decodeResponse(response, _))

private def mediaTypeFromContentTypeHeader(header: HeaderOps[_]) = {
private def mediaTypeFromContentTypeHeader(header: HeaderOps[_]) =
if (header.headers.contains(Header.ContentType.name)) {
val contentType = header.headers.getUnsafe(Header.ContentType.name)
if (MediaType.contentTypeMap.contains(contentType)) {
Expand All @@ -69,41 +68,38 @@ sealed trait HttpContentCodec[A](
} else {
MediaType.application.`json`
}
}

def encode(value: A, config: CodecConfig = CodecConfig.defaultConfig): Either[String, Body] = {
def encode(value: A, config: CodecConfig = CodecConfig.defaultConfig): Either[String, Body] =
if (choices.isEmpty) {
Left("No codec defined")
} else {
Right(Body.fromChunk(choices.head._2.codec(config).encode(value), mediaType = choices.head._1))
}
}

def only(mediaType: MediaType): HttpContentCodec[A] =
HttpContentCodec(
ListMap(
mediaType -> lookup(mediaType)
.getOrElse(
throw new IllegalArgumentException(s"MediaType $mediaType is not supported by $self"),
),
),
throw new IllegalArgumentException(s"MediaType $mediaType is not supported by $self")
)
)
)

def only(mediaType: Option[MediaType]): HttpContentCodec[A] = {
def only(mediaType: Option[MediaType]): HttpContentCodec[A] =
mediaType match {
case Some(mediaType) =>
HttpContentCodec(
ListMap(
mediaType -> lookup(mediaType)
.getOrElse(
throw new IllegalArgumentException(s"MediaType $mediaType is not supported by $self"),
),
),
throw new IllegalArgumentException(s"MediaType $mediaType is not supported by $self")
)
)
)
case None =>
case None =>
self
}
}

private[http] def chooseFirst(mediaTypes: Chunk[MediaTypeWithQFactor]): (MediaType, BinaryCodecWithSchema[A]) =
if (mediaTypes.isEmpty) {
Expand All @@ -125,7 +121,7 @@ sealed trait HttpContentCodec[A](
}

private[http] def chooseFirstOrDefault(
mediaTypes: Chunk[MediaTypeWithQFactor],
mediaTypes: Chunk[MediaTypeWithQFactor]
): (MediaType, BinaryCodecWithSchema[A]) =
if (mediaTypes.isEmpty) {
(defaultMediaType, defaultBinaryCodecWithSchema)
Expand All @@ -145,15 +141,14 @@ sealed trait HttpContentCodec[A](
}
}

def lookup(mediaType: MediaType): Option[BinaryCodecWithSchema[A]] = {
def lookup(mediaType: MediaType): Option[BinaryCodecWithSchema[A]] =
if (lookupCache.contains(mediaType)) {
lookupCache(mediaType)
} else {
val codec = choices.collectFirst { case (mt, codec) if mt.matches(mediaType) => codec }
lookupCache = lookupCache + (mediaType -> codec)
codec
}
}

private[http] val defaultMediaType: MediaType =
choices.headOption.map(_._1).getOrElse {
Expand Down Expand Up @@ -181,27 +176,32 @@ object HttpContentCodec {

def from[A](
codec: (MediaType, BinaryCodecWithSchema[A]),
codecs: (MediaType, BinaryCodecWithSchema[A])*,
codecs: (MediaType, BinaryCodecWithSchema[A])*
): HttpContentCodec[A] =
Default(ListMap((codec +: codecs): _*))

def fromSchema[A](implicit schema: Schema[A]): HttpContentCodec[A] = {
schemaCache.computeIfAbsent(schema, _ => json.only[A] ++ protobuf.only[A] ++ text.only[A])
def fromSchema[A](implicit schema: Schema[A]): HttpContentCodec[A] =
schemaCache
.getOrElseUpdate(schema, _ => json.only[A] ++ protobuf.only[A] ++ text.only[A])
.asInstanceOf[HttpContentCodec[A]]
}

private final case class Default[A](
final private case class Default[A](
choices: ListMap[MediaType, BinaryCodecWithSchema[A]]
) extends HttpContentCodec[A]

final case class Filtered[A](codec: HttpContentCodec[A], mediaType: MediaType) extends HttpContentCodec[A] {
override def choices: ListMap[MediaType, BinaryCodecWithSchema[A]] =
ListMap(mediaType -> codec.lookup(mediaType).getOrElse(
throw new IllegalArgumentException(s"MediaType $mediaType is not supported by $codec")
))
ListMap(
mediaType -> codec
.lookup(mediaType)
.getOrElse(
throw new IllegalArgumentException(s"MediaType $mediaType is not supported by $codec")
)
)
}

object json {

def only[A](implicit schema: Schema[A]): HttpContentCodec[A] =
fromSingleCodec(
MediaType.application.`json`,
Expand All @@ -211,6 +211,7 @@ object HttpContentCodec {
}

object protobuf {

def only[A](implicit schema: Schema[A]): HttpContentCodec[A] =
fromSingleCodec(
MediaType.parseCustomMediaType("application/protobuf").get,
Expand All @@ -220,25 +221,24 @@ object HttpContentCodec {
}

object text {

def only[A](implicit schema: Schema[A]): HttpContentCodec[A] =
fromMultipleCodecs(
List(
MediaType.text.`plain` -> TextBinaryCodec.fromSchema(schema),
MediaType.text.`plain` -> TextBinaryCodec.fromSchema(schema),
MediaType.application.`octet-stream` -> TextBinaryCodec.fromSchema(schema)
),
schema
)
}

private def fromSingleCodec[A](mediaType: MediaType, codec: BinaryCodec[A], schema: Schema[A]) = {
private def fromSingleCodec[A](mediaType: MediaType, codec: BinaryCodec[A], schema: Schema[A]) =
Default(ListMap(mediaType -> BinaryCodecWithSchema(codec, schema)))
}

private def fromMultipleCodecs[A](codecs: List[(MediaType, BinaryCodec[A])], schema: Schema[A]) = {
private def fromMultipleCodecs[A](codecs: List[(MediaType, BinaryCodec[A])], schema: Schema[A]) =
Default(ListMap(codecs.map { case (mt, codec) => mt -> BinaryCodecWithSchema(codec, schema) }: _*))
}

private final case class DefaultCodecError(name: String, message: String)
final private case class DefaultCodecError(name: String, message: String)

private object DefaultCodecError {
implicit val schema: Schema[DefaultCodecError] = DeriveSchema.gen[DefaultCodecError]
Expand Down Expand Up @@ -266,103 +266,100 @@ object HttpContentCodec {
case (Some(name), Some(message)) => Right(HttpCodecError.CustomError(name, message))
case _ => Left("Could not extract name and message from the DOM")
}
},
{
}, {
case HttpCodecError.CustomError(name, message) =>
Right(
html(
body(
h1("Codec Error"),
p("There was an error en-/decoding the request/response"),
p(name, idAttr := "name"),
p(message, idAttr := "message"),
),
),
p(name, idAttr := "name"),
p(message, idAttr := "message")
)
)
)
case e: HttpCodecError =>
case e: HttpCodecError =>
Right(
html(
body(
h1("Codec Error"),
p("There was an error en-/decoding the request/response"),
p(e.productPrefix, idAttr := "name"),
p(e.getMessage(), idAttr := "message"),
),
),
p(e.getMessage(), idAttr := "message")
)
)
)
},
}
)

private val defaultCodecErrorSchema: Schema[HttpCodecError] =
Schema[DefaultCodecError].transformOrFail[HttpCodecError](
codecError => Right(HttpCodecError.CustomError(codecError.name, codecError.message)),
{
codecError => Right(HttpCodecError.CustomError(codecError.name, codecError.message)), {
case HttpCodecError.CustomError(name, message) => Right(DefaultCodecError(name, message))
case e: HttpCodecError => Right(DefaultCodecError(e.productPrefix, e.getMessage()))
},
}
)

private val defaultHttpContentCodec: HttpContentCodec[HttpCodecError] =
HttpContentCodec.from(
MediaType.text.`html` -> BinaryCodecWithSchema(TextBinaryCodec.fromSchema(domBasedSchema), domBasedSchema),
MediaType.text.`html` -> BinaryCodecWithSchema(TextBinaryCodec.fromSchema(domBasedSchema), domBasedSchema),
MediaType.application.json -> BinaryCodecWithSchema(
JsonCodec.schemaBasedBinaryCodec(defaultCodecErrorSchema),
defaultCodecErrorSchema,
),
defaultCodecErrorSchema
)
)

val responseErrorCodec: HttpCodec[HttpCodecType.ResponseType, HttpCodecError] =
ContentCodec.content(defaultHttpContentCodec) ++ StatusCodec.BadRequest

def from[A](
codec: (MediaType, BinaryCodecWithSchema[A]),
codecs: (MediaType, BinaryCodecWithSchema[A])*,
codecs: (MediaType, BinaryCodecWithSchema[A])*
): HttpContentCodec[A] =
HttpContentCodec(ListMap((codec +: codecs): _*))

implicit def fromSchema[A](implicit schema: Schema[A]): HttpContentCodec[A] = {
implicit def fromSchema[A](implicit schema: Schema[A]): HttpContentCodec[A] =
json.only[A] ++ protobuf.only[A] ++ text.only[A]
}

object json {
def only[A](implicit schema: Schema[A]): HttpContentCodec[A] = {

def only[A](implicit schema: Schema[A]): HttpContentCodec[A] =
HttpContentCodec(
ListMap(
MediaType.application.`json` ->
BinaryCodecWithSchema(
config =>
JsonCodec.schemaBasedBinaryCodec[A](
JsonCodec.Config(ignoreEmptyCollections = config.ignoreEmptyCollections),
JsonCodec.Config(ignoreEmptyCollections = config.ignoreEmptyCollections)
)(schema),
schema,
),
),
schema
)
)
)
}
}

object protobuf {
def only[A](implicit schema: Schema[A]): HttpContentCodec[A] = {

def only[A](implicit schema: Schema[A]): HttpContentCodec[A] =
HttpContentCodec(
ListMap(
MediaType.parseCustomMediaType("application/protobuf").get ->
BinaryCodecWithSchema(ProtobufCodec.protobufCodec[A], schema),
),
BinaryCodecWithSchema(ProtobufCodec.protobufCodec[A], schema)
)
)
}
}

object text {
def only[A](implicit schema: Schema[A]): HttpContentCodec[A] = {

def only[A](implicit schema: Schema[A]): HttpContentCodec[A] =
HttpContentCodec(
ListMap(
MediaType.text.`plain` ->
MediaType.text.`plain` ->
BinaryCodecWithSchema(zio.http.codec.internal.TextBinaryCodec.fromSchema[A](schema), schema),
MediaType.application.`octet-stream` ->
BinaryCodecWithSchema(zio.http.codec.internal.TextBinaryCodec.fromSchema[A](schema), schema),
),
BinaryCodecWithSchema(zio.http.codec.internal.TextBinaryCodec.fromSchema[A](schema), schema)
)
)
}
}

private val ByteChunkBinaryCodec: BinaryCodec[Chunk[Byte]] = new BinaryCodec[Chunk[Byte]] {
Expand All @@ -384,8 +381,8 @@ object HttpContentCodec {
ListMap(
MediaType.allMediaTypes
.filter(_.binary)
.map(mt => mt -> BinaryCodecWithSchema(ByteChunkBinaryCodec, Schema.chunk[Byte])): _*,
),
.map(mt => mt -> BinaryCodecWithSchema(ByteChunkBinaryCodec, Schema.chunk[Byte])): _*
)
)
}

Expand All @@ -408,8 +405,8 @@ object HttpContentCodec {
ListMap(
MediaType.allMediaTypes
.filter(_.binary)
.map(mt => mt -> BinaryCodecWithSchema(ByteBinaryCodec, Schema[Byte])): _*,
),
.map(mt => mt -> BinaryCodecWithSchema(ByteBinaryCodec, Schema[Byte])): _*
)
)
}
}

0 comments on commit b523a81

Please sign in to comment.