`text/plain` response body encoding silent failure
Describe the bug
I am using the Endpoint definition to accept some body and return a case class.
I defined the schema and some json encoders.
If a user makes a request with Accept: plain/text the route will return a 500 with no body.
(I had to copy the endpoint code and add log statements to see what the actual error was and it showed that is TextBinary encoder failing - see screenshot)
To Reproduce Steps to reproduce the behaviour:
case class MyItem(id: String, name: String)
object MyItem {
implicit val schema: zio.schema.Schema[MyItem] = zio.schema.DeriveSchema.gen
implicit val encoder: JsonEncoder[MyItem] = JsonCodec.jsonEncoder(schema)
implicit val decoder: JsonDecoder[MyItem] = JsonCodec.jsonDecoder(schema)
}
val getEndpoint =
Endpoint(
RoutePattern.GET / "api" / "item" / PathCodec.string("itemId") ?? Doc.p("Get Item")
).out[MyItem]
val getItemHandler = handler { (itemId: String) =>
ZIO.succeed(MyItem(itemId, "name"))
}
val getItemRoute = getEndpoint.implementHandler(getItemHandler)
val routes = Routes(getItemRoute)
Server.serve(routes)
$ curl localhost:8080/api/item/id -H 'accept:text/plain'
This will return a 500 with no body (and NO LOGS) on the server.
if you explicity set text plain
implicit val codec: zio.json.JsonCodec[MyItem] = JsonCodec.jsonCodec(schema)
implicit val zioHttpContentCodec: HttpContentCodec[MyItem] = {
val supportPlainText =
HttpContentCodec(
ListMap(
MediaType.text.plain ->
BinaryCodecWithSchema(
zio.schema.codec.JsonCodec.zioJsonBinaryCodec[MyItem],
schema
)
)
)
HttpContentCodec.fromSchema ++ supportPlainText
}
then you can curl this way
as a side note, the client sent a list of accept content including application/json Yet the server decided to pick the first from the list, fail silently, and not retry another content type.
Another note - if you define the route explicitly (not using endpoint) - it will not fail! Expected behaviour better: Either a server log or some information best: Support text/plain automatically
Screenshots
You are getting no errors because encoding into the body response is turned off, it can leak server details. ("Secure by default")
It can be set it though with ErrorResponseConfig introduced in this commit:
https://github.com/zio/zio-http/commit/2c90dde99d04dd138370d5ac39ec9ea514565579#diff-c3252812e3130ed03f83725d8f8fe134295b999de4d4de33374072966250700c
So if you upgrade to at least RC10, you can add this as an aspect to your route, something like:
val getItemRoute = getEndpoint.implementHandler(getItemHandler).toRoutes @@ ErrorResponseConfig.debug
Check the documentation about the attributes here: https://github.com/zio/zio-http/blob/main/docs/reference/response/response.md#failure-responses-with-details
On the other hand your code is trying to use a MyItem as a response out, which is not supported by the TextBinaryCodec, that is chosen by zio-http. This is based on your only explicit headerβs mediatype (it is using the first passed or the default one: https://github.com/zio/zio-http/blob/main/zio-http/shared/src/main/scala/zio/http/codec/HttpContentCodec.scala#L113).
So if you are not sending the text/plain header or application/json is preceding that you are good to go, or if you want to use that text/plain header then specify a suitable output like a String.
Are you saying that by default zio-http will only support json encoding but will not support text/plain codec by default?
Why is it that if I do not use the Endpoint but use a direct implementation, that text/plain does not break the server? This seems like an inconsistency but I could misunderstanding.
It was not clear from the links you posted if there is a setting to at least print error logs to the console (and not in the response)
@lemony312 Your direct impl returns a json for text/plain which you can ofc, but is not an expected behavior and therefore not the default. The default is returning a simple String which does not support multi field case classes
@987Nabil Since this is a codec error, can't we do better reporting without leaking details?
/bounty $250
π $250 bounty β’ ZIO
Steps to solve:
- Start working: Comment
/attempt #3188with your implementation plan - Submit work: Create a pull request including
/claim #3188in the PR body to claim the bounty - Receive payment: 100% of the bounty is received 2-5 days post-reward. Make sure you are eligible for payouts
β Important guidelines:
- To claim a bounty, you need to provide a short demo video of your changes in your pull request
- If anything is unclear, ask for clarification before starting as this will help avoid potential rework
- Low quality AI PRs will not receive review and will be closed
- Do not ask to be assigned unless you've contributed before
Thank you for contributing to zio/zio-http!
| Attempt | Started (UTC) | Solution | Actions |
|---|---|---|---|
| π’ @asr2003 | Nov 09, 2024, 09:25:46 PM | WIP | |
| π΄ @deekshatomer | Feb 16, 2025, 12:52:46 AM | WIP | |
| π’ @brndt | Aug 16, 2025, 07:34:05 PM | #3571 | Reward |
/attempt #3188
| Algora profile | Completed bounties | Tech | Active attempts | Options |
|---|---|---|---|---|
| @asr2003 | Β Β Β 3 ZIO bounties + 5 bounties from 3 projects |
Rust, Scala, Go & more |
οΉ350 |
Cancel attempt |
[!NOTE] The user @asr2003 is already attempting to complete issue #3188 and claim the bounty. We recommend checking in on @asr2003's progress, and potentially collaborating, before starting a new solution.
"Hi @maintainer, I see that @asr2003 is working on issue #3188. Have they made significant progress, or is there still room for another contributor to work on this separately?"
@deekshatomer: Reminder that in 7 days the bounty will become up for grabs, so please submit a pull request before then π
/attempt #3188
@brndt: You've been awarded a $250 by ZIO! π Complete your Algora onboarding to collect the bounty.