Skip to content

Commit

Permalink
Simplify Encoderdecoder (#2998)
Browse files Browse the repository at this point in the history
Co-authored-by: pablf <pablofemenia@proton.me>
  • Loading branch information
pablf and pablf authored Aug 23, 2024
1 parent 544adf0 commit da8d8be
Show file tree
Hide file tree
Showing 3 changed files with 488 additions and 557 deletions.
15 changes: 15 additions & 0 deletions zio-http/shared/src/main/scala/zio/http/FormField.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,21 @@ sealed trait FormField {
ZIO.succeed(Chunk.fromArray(value.getBytes(Charsets.Utf8)))
}

/**
* Gets the value of this form field as a chunk of bytes. If it is a text
* field, the value gets encoded as an UTF-8 byte stream.
*/
final def asStream(implicit trace: Trace): ZStream[Any, Nothing, Byte] = this match {
case FormField.Text(_, value, _, _) =>
ZStream.fromChunk(Chunk.fromArray(value.getBytes(Charsets.Utf8)))
case FormField.Binary(_, value, _, _, _) =>
ZStream.fromChunk(value)
case FormField.StreamingBinary(_, _, _, _, stream) =>
stream
case FormField.Simple(_, value) =>
ZStream.fromChunk(Chunk.fromArray(value.getBytes(Charsets.Utf8)))
}

def name(newName: String): FormField = this match {
case FormField.Binary(_, data, contentType, transferEncoding, filename) =>
FormField.Binary(newName, data, contentType, transferEncoding, filename)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import zio.schema._

import zio.http.Header.Accept.MediaTypeWithQFactor
import zio.http.codec.{BinaryCodecWithSchema, HttpCodecError, HttpContentCodec}
import zio.http.{Body, MediaType}
import zio.http.{Body, FormField, MediaType}

/**
* A BodyCodec encapsulates the logic necessary to both encode and decode bodies
Expand All @@ -39,11 +39,21 @@ private[http] sealed trait BodyCodec[A] { self =>
*/
type Element

/**
* Attempts to decode the `A` from a FormField using the given codec.
*/
def decodeFromField(field: FormField)(implicit trace: Trace): IO[Throwable, A]

/**
* Attempts to decode the `A` from a body using the given codec.
*/
def decodeFromBody(body: Body)(implicit trace: Trace): IO[Throwable, A]

/**
* Encodes the `A` to a FormField in the given codec.
*/
def encodeToField(value: A, mediaTypes: Chunk[MediaTypeWithQFactor], name: String)(implicit trace: Trace): FormField

/**
* Encodes the `A` to a body in the given codec.
*/
Expand Down Expand Up @@ -74,8 +84,15 @@ private[http] object BodyCodec {
case object Empty extends BodyCodec[Unit] {
type Element = Unit

def decodeFromField(field: FormField)(implicit trace: Trace): IO[Throwable, Unit] = ZIO.unit

def decodeFromBody(body: Body)(implicit trace: Trace): IO[Nothing, Unit] = ZIO.unit

def encodeToField(value: Unit, mediaTypes: Chunk[MediaTypeWithQFactor], name: String)(implicit
trace: Trace,
): FormField =
throw HttpCodecError.CustomError("UnsupportedEncodingType", s"Unit can't be encoded to a FormField")

def encodeToBody(value: Unit, mediaTypes: Chunk[MediaTypeWithQFactor])(implicit trace: Trace): Body = Body.empty

def schema: Schema[Unit] = Schema[Unit]
Expand All @@ -90,6 +107,19 @@ private[http] object BodyCodec {
def mediaType(accepted: Chunk[MediaTypeWithQFactor]): Option[MediaType] =
Some(codec.chooseFirstOrDefault(accepted)._1)

def decodeFromField(field: FormField)(implicit trace: Trace): IO[Throwable, A] = {
val codec0 = codec
.lookup(field.contentType)
.toRight(HttpCodecError.CustomError("UnsupportedMediaType", s"MediaType: ${field.contentType}"))
codec0 match {
case Left(error) => ZIO.fail(error)
case Right(BinaryCodecWithSchema(_, schema)) if schema == Schema[Unit] =>
ZIO.unit.asInstanceOf[IO[Throwable, A]]
case Right(BinaryCodecWithSchema(codec, schema)) =>
field.asChunk.flatMap { chunk => ZIO.fromEither(codec.decode(chunk)) }.flatMap(validateZIO(schema))
}
}

def decodeFromBody(body: Body)(implicit trace: Trace): IO[Throwable, A] = {
val codec0 = codecForBody(codec, body)
codec0 match {
Expand All @@ -101,8 +131,27 @@ private[http] object BodyCodec {
}
}

def encodeToField(value: A, mediaTypes: Chunk[MediaTypeWithQFactor], name: String)(implicit
trace: Trace,
): FormField = {
val (mediaType, BinaryCodecWithSchema(codec0, _)) = codec.chooseFirstOrDefault(mediaTypes)
if (mediaType.binary) {
FormField.binaryField(
name,
codec0.encode(value),
mediaType,
)
} else {
FormField.textField(
name,
codec0.encode(value).asString,
mediaType,
)
}
}

def encodeToBody(value: A, mediaTypes: Chunk[MediaTypeWithQFactor])(implicit trace: Trace): Body = {
val (mediaType, bc @ BinaryCodecWithSchema(_, _)) = codec.chooseFirst(mediaTypes)
val (mediaType, bc @ BinaryCodecWithSchema(_, _)) = codec.chooseFirstOrDefault(mediaTypes)
Body.fromChunk(bc.codec.encode(value)).contentType(mediaType)
}

Expand All @@ -115,20 +164,40 @@ private[http] object BodyCodec {
def mediaType(accepted: Chunk[MediaTypeWithQFactor]): Option[MediaType] =
Some(codec.chooseFirstOrDefault(accepted)._1)

def decodeFromField(field: FormField)(implicit trace: Trace): IO[Throwable, ZStream[Any, Nothing, E]] =
ZIO.fromEither {
codec
.lookup(field.contentType)
.toRight(HttpCodecError.CustomError("UnsupportedMediaType", s"MediaType: ${field.contentType}"))
.map { case BinaryCodecWithSchema(codec, schema) =>
(field.asStream >>> codec.streamDecoder >>> validateStream(schema)).orDie
}
}

def decodeFromBody(body: Body)(implicit
trace: Trace,
): IO[Throwable, ZStream[Any, Nothing, E]] = {
): IO[Throwable, ZStream[Any, Nothing, E]] =
ZIO.fromEither {
codecForBody(codec, body).map { case bc @ BinaryCodecWithSchema(_, schema) =>
(body.asStream >>> bc.codec.streamDecoder >>> validateStream(schema)).orDie
}
}

def encodeToField(value: ZStream[Any, Nothing, E], mediaTypes: Chunk[MediaTypeWithQFactor], name: String)(implicit
trace: Trace,
): FormField = {
val (mediaType, BinaryCodecWithSchema(codec0, _)) = codec.chooseFirstOrDefault(mediaTypes)
FormField.streamingBinaryField(
name,
value >>> codec0.streamEncoder,
mediaType,
)
}

def encodeToBody(value: ZStream[Any, Nothing, E], mediaTypes: Chunk[MediaTypeWithQFactor])(implicit
trace: Trace,
): Body = {
val (mediaType, bc @ BinaryCodecWithSchema(_, _)) = codec.chooseFirst(mediaTypes)
val (mediaType, bc @ BinaryCodecWithSchema(_, _)) = codec.chooseFirstOrDefault(mediaTypes)
Body.fromStreamChunked(value >>> bc.codec.streamEncoder).contentType(mediaType)
}

Expand Down
Loading

0 comments on commit da8d8be

Please sign in to comment.