From 7f68c32839e0950d2c1a5ab79f8f52d56e8631ff Mon Sep 17 00:00:00 2001 From: Vamshi Maskuri <117595548+varshith257@users.noreply.github.com> Date: Sun, 10 Nov 2024 01:26:21 +0530 Subject: [PATCH 1/8] fix:fmt --- .../scala/zio/http/ErrorResponseConfig.scala | 3 +- .../zio/http/codec/HttpContentCodec.scala | 55 +++++++++++++------ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/zio-http/shared/src/main/scala/zio/http/ErrorResponseConfig.scala b/zio-http/shared/src/main/scala/zio/http/ErrorResponseConfig.scala index 10237403a8..7f8e39f55c 100644 --- a/zio-http/shared/src/main/scala/zio/http/ErrorResponseConfig.scala +++ b/zio-http/shared/src/main/scala/zio/http/ErrorResponseConfig.scala @@ -22,6 +22,7 @@ final case class ErrorResponseConfig( withStackTrace: Boolean = false, maxStackTraceDepth: Int = 10, errorFormat: ErrorResponseConfig.ErrorFormat = ErrorResponseConfig.ErrorFormat.Html, + logCodecErrors: Boolean = false, ) object ErrorResponseConfig { @@ -34,7 +35,7 @@ object ErrorResponseConfig { val default: ErrorResponseConfig = ErrorResponseConfig() val debugConfig: ErrorResponseConfig = - ErrorResponseConfig(withErrorBody = true, withStackTrace = true, maxStackTraceDepth = 0) + ErrorResponseConfig(withErrorBody = true, withStackTrace = true, maxStackTraceDepth = 0, logCodecErrors = true) private[http] val configRef: FiberRef[ErrorResponseConfig] = FiberRef.unsafe.make(default)(Unsafe) diff --git a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala index 3f8a3f29a2..fe09bb3965 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala @@ -28,14 +28,21 @@ sealed trait HttpContentCodec[A] { self => HttpContentCodec.Choices(choices ++ that.choices) def decodeRequest(request: Request, config: CodecConfig): Task[A] = { - val contentType = mediaTypeFromContentTypeHeader(request) - lookup(contentType) match { - case Some((_, codec)) => - request.body.asChunk.flatMap { bytes => - ZIO.fromEither(codec.codec(config).decode(bytes)) - } - case None => - ZIO.fail(throw new IllegalArgumentException(s"No codec found for content type $contentType")) + ErrorResponseConfig.configRef.get.flatMap { config => + val contentType = mediaTypeFromContentTypeHeader(request) + lookup(contentType) match { + case Some((_, codec)) => + request.body.asChunk.flatMap { bytes => + ZIO + .fromEither(codec.codec(config).decode(bytes)) + .mapError(_ => CodecDecodeError(s"Failed to decode request body for media type: $contentType")) + .tapError(error => if (config.logCodecErrors) ZIO.logWarning(error.getMessage) else ZIO.unit) + } + case None => + ZIO + .fail(UnsupportedMediaTypeError(contentType)) + .tapError(error => if (config.logCodecErrors) ZIO.logWarning(error.getMessage) else ZIO.unit) + } } } @@ -43,14 +50,21 @@ sealed trait HttpContentCodec[A] { self => CodecConfig.codecRef.getWith(decodeRequest(request, _)) def decodeResponse(response: Response, config: CodecConfig): Task[A] = { - val contentType = mediaTypeFromContentTypeHeader(response) - lookup(contentType) match { - case Some((_, codec)) => - response.body.asChunk.flatMap { bytes => - ZIO.fromEither(codec.codec(config).decode(bytes)) - } - case None => - ZIO.fail(throw new IllegalArgumentException(s"No codec found for content type $contentType")) + ErrorResponseConfig.configRef.get.flatMap { config => + val contentType = mediaTypeFromContentTypeHeader(response) + lookup(contentType) match { + case Some((_, codec)) => + response.body.asChunk.flatMap { bytes => + ZIO + .fromEither(codec.codec(config).decode(bytes)) + .mapError(_ => CodecDecodeError(s"Failed to decode response body for media type: $contentType")) + .tapError(error => if (config.logCodecErrors) ZIO.logWarning(error.getMessage) else ZIO.unit) + } + case None => + ZIO + .fail(UnsupportedMediaTypeError(contentType)) + .tapError(error => if (config.logCodecErrors) ZIO.logWarning(error.getMessage) else ZIO.unit) + } } } @@ -180,6 +194,15 @@ sealed trait HttpContentCodec[A] { self => } +sealed trait CodecError extends Throwable +case class UnsupportedMediaTypeError(mediaType: MediaType) extends CodecError { + override def getMessage: String = s"Unsupported media type: $mediaType" +} + +case class CodecDecodeError(details: String) extends CodecError { + override def getMessage: String = s"Codec decode error: $details" +} + object HttpContentCodec { final case class Choices[A]( choices: ListMap[MediaType, BinaryCodecWithSchema[A]], From 5d436b82cedf857585c7d6b8ae6dd465236d5172 Mon Sep 17 00:00:00 2001 From: Vamshi Maskuri <117595548+varshith257@users.noreply.github.com> Date: Sun, 10 Nov 2024 01:34:10 +0530 Subject: [PATCH 2/8] fix:fmt --- .../zio/http/codec/HttpContentCodec.scala | 68 +++++++++++-------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala index fe09bb3965..1bfe385795 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala @@ -28,21 +28,27 @@ sealed trait HttpContentCodec[A] { self => HttpContentCodec.Choices(choices ++ that.choices) def decodeRequest(request: Request, config: CodecConfig): Task[A] = { - ErrorResponseConfig.configRef.get.flatMap { config => - val contentType = mediaTypeFromContentTypeHeader(request) - lookup(contentType) match { - case Some((_, codec)) => - request.body.asChunk.flatMap { bytes => - ZIO - .fromEither(codec.codec(config).decode(bytes)) - .mapError(_ => CodecDecodeError(s"Failed to decode request body for media type: $contentType")) - .tapError(error => if (config.logCodecErrors) ZIO.logWarning(error.getMessage) else ZIO.unit) - } - case None => + val contentType = mediaTypeFromContentTypeHeader(request) + lookup(contentType) match { + case Some((_, codec)) => + request.body.asChunk.flatMap { bytes => ZIO - .fail(UnsupportedMediaTypeError(contentType)) - .tapError(error => if (config.logCodecErrors) ZIO.logWarning(error.getMessage) else ZIO.unit) - } + .fromEither(codec.codec(config).decode(bytes)) + .mapError(_ => CodecDecodeError(s"Failed to decode request body for media type: $contentType")) + .tapError(error => + ErrorResponseConfig.configRef.get.flatMap { errConfig => + if (errConfig.logCodecErrors) ZIO.logWarning(error.getMessage) else ZIO.unit + }, + ) + } + case None => + ZIO + .fail(UnsupportedMediaTypeError(contentType)) + .tapError(error => + ErrorResponseConfig.configRef.get.flatMap { errConfig => + if (errConfig.logCodecErrors) ZIO.logWarning(error.getMessage) else ZIO.unit + }, + ) } } @@ -50,21 +56,27 @@ sealed trait HttpContentCodec[A] { self => CodecConfig.codecRef.getWith(decodeRequest(request, _)) def decodeResponse(response: Response, config: CodecConfig): Task[A] = { - ErrorResponseConfig.configRef.get.flatMap { config => - val contentType = mediaTypeFromContentTypeHeader(response) - lookup(contentType) match { - case Some((_, codec)) => - response.body.asChunk.flatMap { bytes => - ZIO - .fromEither(codec.codec(config).decode(bytes)) - .mapError(_ => CodecDecodeError(s"Failed to decode response body for media type: $contentType")) - .tapError(error => if (config.logCodecErrors) ZIO.logWarning(error.getMessage) else ZIO.unit) - } - case None => + val contentType = mediaTypeFromContentTypeHeader(response) + lookup(contentType) match { + case Some((_, codec)) => + response.body.asChunk.flatMap { bytes => ZIO - .fail(UnsupportedMediaTypeError(contentType)) - .tapError(error => if (config.logCodecErrors) ZIO.logWarning(error.getMessage) else ZIO.unit) - } + .fromEither(codec.codec(config).decode(bytes)) + .mapError(_ => CodecDecodeError(s"Failed to decode response body for media type: $contentType")) + .tapError(error => + ErrorResponseConfig.configRef.get.flatMap { errConfig => + if (errConfig.logCodecErrors) ZIO.logWarning(error.getMessage) else ZIO.unit + }, + ) + } + case None => + ZIO + .fail(UnsupportedMediaTypeError(contentType)) + .tapError(error => + ErrorResponseConfig.configRef.get.flatMap { errConfig => + if (errConfig.logCodecErrors) ZIO.logWarning(error.getMessage) else ZIO.unit + }, + ) } } From 1bc8bde39a61cab5852fd197c3078110467e0e8c Mon Sep 17 00:00:00 2001 From: Vamshi Maskuri <117595548+varshith257@users.noreply.github.com> Date: Sun, 10 Nov 2024 02:01:25 +0530 Subject: [PATCH 3/8] fix:fmt --- .../src/main/scala/zio/http/codec/HttpContentCodec.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala index 1bfe385795..7bcb1b67da 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala @@ -138,9 +138,9 @@ sealed trait HttpContentCodec[A] { self => private[http] def chooseFirstOrDefault( mediaTypes: Chunk[MediaTypeWithQFactor], - ): (MediaType, BinaryCodecWithSchema[A]) = + ): Either[UnsupportedMediaTypeError, (MediaType, BinaryCodecWithSchema[A])] = if (mediaTypes.isEmpty) { - (defaultMediaType, defaultBinaryCodecWithSchema) + Right((defaultMediaType, defaultBinaryCodecWithSchema)) } else { var i = 0 var result: (MediaType, BinaryCodecWithSchema[A]) = null @@ -150,8 +150,8 @@ sealed trait HttpContentCodec[A] { self => if (lookupResult.isDefined) result = lookupResult.get i += 1 } - if (result == null) (defaultMediaType, defaultBinaryCodecWithSchema) - else result + if (result == null) Left(UnsupportedMediaTypeError(mediaTypes.map(_.mediaType).mkString(", "))) + else Right(result) } def lookup(mediaType: MediaType): Option[(MediaType, BinaryCodecWithSchema[A])] From d8249a47ed842d82048431396a72e1097c9672e2 Mon Sep 17 00:00:00 2001 From: Vamshi Maskuri <117595548+varshith257@users.noreply.github.com> Date: Sun, 10 Nov 2024 02:03:36 +0530 Subject: [PATCH 4/8] fix:fmt --- .../src/main/scala/zio/http/codec/HttpContentCodec.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala index 7bcb1b67da..1bfe385795 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala @@ -138,9 +138,9 @@ sealed trait HttpContentCodec[A] { self => private[http] def chooseFirstOrDefault( mediaTypes: Chunk[MediaTypeWithQFactor], - ): Either[UnsupportedMediaTypeError, (MediaType, BinaryCodecWithSchema[A])] = + ): (MediaType, BinaryCodecWithSchema[A]) = if (mediaTypes.isEmpty) { - Right((defaultMediaType, defaultBinaryCodecWithSchema)) + (defaultMediaType, defaultBinaryCodecWithSchema) } else { var i = 0 var result: (MediaType, BinaryCodecWithSchema[A]) = null @@ -150,8 +150,8 @@ sealed trait HttpContentCodec[A] { self => if (lookupResult.isDefined) result = lookupResult.get i += 1 } - if (result == null) Left(UnsupportedMediaTypeError(mediaTypes.map(_.mediaType).mkString(", "))) - else Right(result) + if (result == null) (defaultMediaType, defaultBinaryCodecWithSchema) + else result } def lookup(mediaType: MediaType): Option[(MediaType, BinaryCodecWithSchema[A])] From 1c834b866326b48d39424961918ca4102de48355 Mon Sep 17 00:00:00 2001 From: Vamshi Maskuri <117595548+varshith257@users.noreply.github.com> Date: Sun, 10 Nov 2024 02:13:41 +0530 Subject: [PATCH 5/8] fix:fmt --- .../src/main/scala/zio/http/codec/HttpContentCodec.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala index 1bfe385795..73d5f919e8 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala @@ -138,9 +138,9 @@ sealed trait HttpContentCodec[A] { self => private[http] def chooseFirstOrDefault( mediaTypes: Chunk[MediaTypeWithQFactor], - ): (MediaType, BinaryCodecWithSchema[A]) = + ): Either[UnsupportedMediaTypeError, (MediaType, BinaryCodecWithSchema[A])] = if (mediaTypes.isEmpty) { - (defaultMediaType, defaultBinaryCodecWithSchema) + Right((defaultMediaType, defaultBinaryCodecWithSchema)) } else { var i = 0 var result: (MediaType, BinaryCodecWithSchema[A]) = null @@ -150,8 +150,8 @@ sealed trait HttpContentCodec[A] { self => if (lookupResult.isDefined) result = lookupResult.get i += 1 } - if (result == null) (defaultMediaType, defaultBinaryCodecWithSchema) - else result + if (result == null) Left(UnsupportedMediaTypeError(mediaTypes.head.mediaType)) + else Right(result) } def lookup(mediaType: MediaType): Option[(MediaType, BinaryCodecWithSchema[A])] From 4ee3e489ac9601234a42caa6285f42aa0be28a49 Mon Sep 17 00:00:00 2001 From: Vamshi Maskuri <117595548+varshith257@users.noreply.github.com> Date: Sun, 10 Nov 2024 02:24:06 +0530 Subject: [PATCH 6/8] fix:fmt --- .../zio/http/codec/internal/BodyCodec.scala | 77 ++++++++++++------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/zio-http/shared/src/main/scala/zio/http/codec/internal/BodyCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/internal/BodyCodec.scala index feb9f64ee1..fa77a098b5 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/internal/BodyCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/internal/BodyCodec.scala @@ -109,9 +109,11 @@ private[http] object BodyCodec { final case class Single[A](codec: HttpContentCodec[A], name: Option[String]) extends BodyCodec[A] { - def mediaType(accepted: Chunk[MediaTypeWithQFactor]): Option[MediaType] = - Some(codec.chooseFirstOrDefault(accepted)._1) - + def mediaType(accepted: Chunk[MediaTypeWithQFactor]): Option[MediaType] = + codec.chooseFirstOrDefault(accepted) match { + case Right((mediaType, _)) => Some(mediaType) + case Left(_) => None + } def decodeFromField(field: FormField, config: CodecConfig)(implicit trace: Trace): IO[Throwable, A] = { val codec0 = codec .lookup(field.contentType) @@ -139,27 +141,36 @@ private[http] object BodyCodec { def encodeToField(value: A, mediaTypes: Chunk[MediaTypeWithQFactor], name: String, config: CodecConfig)(implicit trace: Trace, ): FormField = { - val (mediaType, bc @ BinaryCodecWithSchema(_, _)) = codec.chooseFirstOrDefault(mediaTypes) - if (mediaType.binary) { - FormField.binaryField( - name, - bc.codec(config).encode(value), - mediaType, - ) - } else { - FormField.textField( - name, - bc.codec(config).encode(value).asString, - mediaType, - ) + codec.chooseFirstOrDefault(mediaTypes) match { + + case Right((mediaType, bc @ BinaryCodecWithSchema(_, _))) => + if (mediaType.binary) { + FormField.binaryField( + name, + bc.codec(config).encode(value), + mediaType, + ) + } else { + FormField.textField( + name, + bc.codec(config).encode(value).asString, + mediaType, + ) + } + case Left(error) => + throw new IllegalArgumentException(s"Unsupported media type: ${error.mediaType}") } } def encodeToBody(value: A, mediaTypes: Chunk[MediaTypeWithQFactor], config: CodecConfig)(implicit trace: Trace, ): Body = { - val (mediaType, bc @ BinaryCodecWithSchema(_, _)) = codec.chooseFirstOrDefault(mediaTypes) - Body.fromChunk(bc.codec(config).encode(value), mediaType) + codec.chooseFirstOrDefault(mediaTypes) match { + case Right((mediaType, bc @ BinaryCodecWithSchema(_, _))) => + Body.fromChunk(bc.codec(config).encode(value), mediaType) + case Left(error) => + throw new IllegalArgumentException(s"Unsupported media type: ${error.mediaType}") + } } type Element = A @@ -169,8 +180,10 @@ private[http] object BodyCodec { extends BodyCodec[ZStream[Any, Nothing, E]] { def mediaType(accepted: Chunk[MediaTypeWithQFactor]): Option[MediaType] = - Some(codec.chooseFirstOrDefault(accepted)._1) - + codec.chooseFirstOrDefault(accepted) match { + case Right((mediaType, _)) => Some(mediaType) + case Left(_) => None + } override def decodeFromField(field: FormField, config: CodecConfig)(implicit trace: Trace, ): IO[Throwable, ZStream[Any, Nothing, E]] = @@ -200,12 +213,16 @@ private[http] object BodyCodec { )(implicit trace: Trace, ): FormField = { - val (mediaType, bc) = codec.chooseFirstOrDefault(mediaTypes) - FormField.streamingBinaryField( - name, - value >>> bc.codec(config).streamEncoder, - mediaType, - ) + codec.chooseFirstOrDefault(mediaTypes) match { + case Right((mediaType, bc)) => + FormField.streamingBinaryField( + name, + value >>> bc.codec(config).streamEncoder, + mediaType, + ) + case Left(error) => + throw new IllegalArgumentException(s"Unsupported media type: ${error.mediaType}") + } } override def encodeToBody( @@ -215,8 +232,12 @@ private[http] object BodyCodec { )(implicit trace: Trace, ): Body = { - val (mediaType, bc @ BinaryCodecWithSchema(_, _)) = codec.chooseFirstOrDefault(mediaTypes) - Body.fromStreamChunked(value >>> bc.codec(config).streamEncoder).contentType(mediaType) + codec.chooseFirstOrDefault(mediaTypes) match { + case Right((mediaType, bc @ BinaryCodecWithSchema(_, _))) => + Body.fromStreamChunked(value >>> bc.codec(config).streamEncoder).contentType(mediaType) + case Left(error) => + throw new IllegalArgumentException(s"Unsupported media type: ${error.mediaType}") + } } type Element = E From 37befc41d91815c6ebd848a80c128b6e2a57ea31 Mon Sep 17 00:00:00 2001 From: Vamshi Maskuri <117595548+varshith257@users.noreply.github.com> Date: Sun, 10 Nov 2024 02:41:50 +0530 Subject: [PATCH 7/8] Implement codec error handling and text/plain support with detailed logging --- .../shared/src/main/scala/zio/http/codec/HttpContentCodec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala index 73d5f919e8..bc435aa008 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala @@ -150,7 +150,7 @@ sealed trait HttpContentCodec[A] { self => if (lookupResult.isDefined) result = lookupResult.get i += 1 } - if (result == null) Left(UnsupportedMediaTypeError(mediaTypes.head.mediaType)) + if (result == null) Right((defaultMediaType, defaultBinaryCodecWithSchema)) else Right(result) } From 27113683c320012d58b7f33a88a99e9bf4b631ed Mon Sep 17 00:00:00 2001 From: Vamshi Maskuri <117595548+varshith257@users.noreply.github.com> Date: Sun, 10 Nov 2024 02:52:35 +0530 Subject: [PATCH 8/8] Implement codec error handling and text/plain support with detailed logging --- .../src/main/scala/zio/http/codec/HttpContentCodec.scala | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala index bc435aa008..30ca37c7ba 100644 --- a/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala +++ b/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala @@ -150,8 +150,10 @@ sealed trait HttpContentCodec[A] { self => if (lookupResult.isDefined) result = lookupResult.get i += 1 } - if (result == null) Right((defaultMediaType, defaultBinaryCodecWithSchema)) - else Right(result) + if (result == null) { + ZIO.logWarning(s"Unsupported media type: ${mediaTypes.head.mediaType}") + Right((defaultMediaType, defaultBinaryCodecWithSchema)) + } else Right(result) } def lookup(mediaType: MediaType): Option[(MediaType, BinaryCodecWithSchema[A])]