Description
toResult() (ApiClientBase.kt) resolves the success body via plain body<T>():
public suspend inline fun <reified E, reified T> HttpResponse.toResult(): HttpResult<E, T> =
mapToResult { body() }
For reified T = String, Ktor's client has a built-in default converter for
String/ByteArray/Unit/HttpStatusCode/ByteReadChannel that decodes the byte stream using
the response charset — bypassing ContentNegotiation entirely, regardless of
Content-Type: application/json. When an endpoint's OpenAPI response schema is a bare
{"type": "string"} and the server sends a JSON string literal (e.g. "abc-123"), the surrounding
quotes land in the decoded value verbatim instead of being stripped by JSON decoding.
Root cause
Generic body<T>() in mapToResult/toResult relies on Ktor's implicit response-transformation
pipeline, which silently diverges for String vs. every other generated model type.
Proposed fix
Stop relying on Ktor's implicit default converter for String. Decode every success body
explicitly and uniformly through the same configured Json instance (the one already carrying
serializersModule, currently only wired into ContentNegotiation inside createHttpClient)
instead of the ambient body<T>():
protected suspend inline fun <reified E, reified T> HttpResponse.toResult(): HttpResult<E, T> =
mapToResult { json.decodeFromString(bodyAsText()) }
This requires promoting toResult/mapToResult from top-level extension functions to members of
ApiClientBase so they can reach the shared json property, and removing the reliance on Ktor's
default converters altogether — not just papering over String with an if.
Acceptance criteria
Discovered in
Split out from #108 — originally reported together as two independent bugs sharing the same
quote-trim anti-pattern. Reproduced against justworks v0.2.5 / commit 088db52.
Description
toResult()(ApiClientBase.kt) resolves the success body via plainbody<T>():For
reified T = String, Ktor's client has a built-in default converter forString/ByteArray/Unit/HttpStatusCode/ByteReadChannelthat decodes the byte stream usingthe response charset — bypassing
ContentNegotiationentirely, regardless ofContent-Type: application/json. When an endpoint's OpenAPI response schema is a bare{"type": "string"}and the server sends a JSON string literal (e.g."abc-123"), the surroundingquotes land in the decoded value verbatim instead of being stripped by JSON decoding.
Root cause
Generic
body<T>()inmapToResult/toResultrelies on Ktor's implicit response-transformationpipeline, which silently diverges for
Stringvs. every other generated model type.Proposed fix
Stop relying on Ktor's implicit default converter for
String. Decode every success bodyexplicitly and uniformly through the same configured
Jsoninstance (the one already carryingserializersModule, currently only wired intoContentNegotiationinsidecreateHttpClient)instead of the ambient
body<T>():This requires promoting
toResult/mapToResultfrom top-level extension functions to members ofApiClientBaseso they can reach the sharedjsonproperty, and removing the reliance on Ktor'sdefault converters altogether — not just papering over
Stringwith anif.Acceptance criteria
String-typed success response containing a JSON-quoted value (e.g."abc-123") decodesto the unquoted value (
abc-123), covered by aMockEnginetestStringsuccess bodies (data classes,List<T>, polymorphiconeOftypes relying onserializersModule) continue to deserialize identically to today — no regression fromswitching off the implicit
body<T>()pathApiClientBaseGeneratorTestand/orJustworksPluginFunctionalTestDiscovered in
Split out from #108 — originally reported together as two independent bugs sharing the same
quote-trim anti-pattern. Reproduced against justworks
v0.2.5/ commit088db52.