Description
A schema property declared as {type: string, format: byte} is mapped to Kotlin ByteArray
(PrimitiveType.BYTE_ARRAY, see SpecParser.kt), but no serializer is generated or registered
for it. Generated @Serializable data classes with such a property compile fine, but don't
decode real API payloads correctly at runtime.
Root cause
Per OpenAPI 3.0, format: byte means "base64-encoded string" — on the wire it's a JSON string
(e.g. "SGVsbG8="). But kotlinx.serialization's built-in ByteArraySerializer (the one picked up
implicitly for a plain ByteArray property) encodes/decodes it as a JSON array of numbers
([72, 101, ...]) instead. Any real API response with a base64 string in that field throws during
deserialization; conversely, anything we serialize ourselves comes out as a number array a real
API wouldn't accept.
This is the same category of bug fixed for Uuid — a JVM/Kotlin type kotlinx.serialization
doesn't have a wire-compatible built-in serializer for — via a generated UuidSerializer +
@Serializable(with = UuidSerializer::class), opt-in only when the spec actually uses it
(ModelGenerator.kt, generateUuidSerializer()).
Proposed fix
Mirror the existing UuidSerializer pattern: generate a shared Base64ByteArraySerializer
(KSerializer<ByteArray> encoding/decoding via base64), opt-in only when the spec uses
format: byte, and annotate matching model properties with
@Serializable(with = Base64ByteArraySerializer::class).
Once this exists, PR #112's top-level-response handling for a bare format: byte schema under
application/json (currently downgraded to String to avoid the same mismatch — see
ClientGenerator.resolveReturnType) could be revisited to decode straight to a real ByteArray
via the shared serializer instead.
Acceptance criteria
Discovered in
PR #112 (review by @MattK97) — a format: byte schema under application/json at the top-level
response was found to throw via decodeFromString<ByteArray>() on every call, since
kotlinx.serialization's default ByteArraySerializer doesn't match the base64-string wire format.
PR #112 fixes the top-level-response symptom by downgrading the return type to String; this
issue covers the more general (and pre-existing, not a regression from #112) gap for ByteArray
properties nested inside model classes, which has the identical root cause.
Description
A schema property declared as
{type: string, format: byte}is mapped to KotlinByteArray(
PrimitiveType.BYTE_ARRAY, seeSpecParser.kt), but no serializer is generated or registeredfor it. Generated
@Serializabledata classes with such a property compile fine, but don'tdecode real API payloads correctly at runtime.
Root cause
Per OpenAPI 3.0,
format: bytemeans "base64-encoded string" — on the wire it's a JSON string(e.g.
"SGVsbG8="). But kotlinx.serialization's built-inByteArraySerializer(the one picked upimplicitly for a plain
ByteArrayproperty) encodes/decodes it as a JSON array of numbers(
[72, 101, ...]) instead. Any real API response with a base64 string in that field throws duringdeserialization; conversely, anything we serialize ourselves comes out as a number array a real
API wouldn't accept.
This is the same category of bug fixed for
Uuid— a JVM/Kotlin type kotlinx.serializationdoesn't have a wire-compatible built-in serializer for — via a generated
UuidSerializer+@Serializable(with = UuidSerializer::class), opt-in only when the spec actually uses it(
ModelGenerator.kt,generateUuidSerializer()).Proposed fix
Mirror the existing
UuidSerializerpattern: generate a sharedBase64ByteArraySerializer(
KSerializer<ByteArray>encoding/decoding via base64), opt-in only when the spec usesformat: byte, and annotate matching model properties with@Serializable(with = Base64ByteArraySerializer::class).Once this exists, PR #112's top-level-response handling for a bare
format: byteschema underapplication/json(currently downgraded toStringto avoid the same mismatch — seeClientGenerator.resolveReturnType) could be revisited to decode straight to a realByteArrayvia the shared serializer instead.
Acceptance criteria
format: bytemodel property round-trips correctly against a real base64 JSON string(covered by a
MockEngine/compilation test)Base64ByteArraySerializeris generated only when the spec actually usesformat: byte(mirroring
usesUuid())ByteArrayproperties/responses underapplication/octet-stream— those areraw bytes already handled correctly via
toRawResult()/body<T>()Discovered in
PR #112 (review by @MattK97) — a
format: byteschema underapplication/jsonat the top-levelresponse was found to throw via
decodeFromString<ByteArray>()on every call, sincekotlinx.serialization's default
ByteArraySerializerdoesn't match the base64-string wire format.PR #112 fixes the top-level-response symptom by downgrading the return type to
String; thisissue covers the more general (and pre-existing, not a regression from #112) gap for
ByteArrayproperties nested inside model classes, which has the identical root cause.