From fb800d809dc1a403e0179948ac728afc996ea283 Mon Sep 17 00:00:00 2001 From: Nikhil Sulegaon Date: Fri, 24 Apr 2026 15:50:30 -0700 Subject: [PATCH 1/2] [Fix][scala-sttp][circe] format: byte (Base64) properties fail to deserialize - Decoder[Array[Byte]] expects JSON array --- .../scala-sttp/additionalTypeSerializers.mustache | 7 +++++++ .../codegen/scala/ScalaSttpCirceCodegenTest.java | 3 +++ .../src/test/resources/3_0/scala/mixed-case-fields.yaml | 3 +++ .../client/core/AdditionalTypeSerializers.scala | 7 +++++++ 4 files changed, 20 insertions(+) diff --git a/modules/openapi-generator/src/main/resources/scala-sttp/additionalTypeSerializers.mustache b/modules/openapi-generator/src/main/resources/scala-sttp/additionalTypeSerializers.mustache index 8d97fa9e105c..12b87cec793b 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp/additionalTypeSerializers.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp/additionalTypeSerializers.mustache @@ -27,6 +27,7 @@ object AdditionalTypeSerializers { {{#circe}} import java.io.File import java.nio.file.Files +import java.util.Base64 trait AdditionalTypeSerializers { import io.circe._ @@ -79,5 +80,11 @@ trait AdditionalTypeSerializers { case "-Infinity" => Right(Double.NegativeInfinity) case s => Left(s"Cannot decode '$s' as Double") }) + + implicit final lazy val Base64OrArrayByteDecoder: Decoder[Array[Byte]] = + Decoder.decodeArray[Byte].or(Decoder.decodeString.emap { s => + try Right(Base64.getDecoder.decode(s)) + catch { case _: IllegalArgumentException => Left(s"Cannot decode '$s' as Base64 Array[Byte]") } + }) } {{/circe}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/ScalaSttpCirceCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/ScalaSttpCirceCodegenTest.java index 9860771dcbae..b1875268f72c 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/ScalaSttpCirceCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/ScalaSttpCirceCodegenTest.java @@ -70,6 +70,7 @@ public void verifyBaseNameFieldMapping() throws IOException { // BinaryPayload: File and untyped object fields Path binaryPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/BinaryPayload.scala"); assertFileContains(binaryPath, "data: Option[File]"); + assertFileContains(binaryPath, "checksum: Option[Array[Byte]]"); assertFileContains(binaryPath, "implicit val encoderBinaryPayload"); assertFileContains(binaryPath, "implicit val decoderBinaryPayload"); @@ -83,6 +84,8 @@ public void verifyBaseNameFieldMapping() throws IOException { assertFileContains(serializersPath, "Double.NaN"); assertFileContains(serializersPath, "Double.PositiveInfinity"); assertFileContains(serializersPath, "Double.NegativeInfinity"); + assertFileContains(serializersPath, "Base64OrArrayByteDecoder"); + assertFileContains(serializersPath, "Base64.getDecoder.decode"); // JsonSupport should NOT use AutoDerivation Path jsonSupportPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/core/JsonSupport.scala"); diff --git a/modules/openapi-generator/src/test/resources/3_0/scala/mixed-case-fields.yaml b/modules/openapi-generator/src/test/resources/3_0/scala/mixed-case-fields.yaml index e5cdbae40845..4802cc24c3e6 100644 --- a/modules/openapi-generator/src/test/resources/3_0/scala/mixed-case-fields.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/scala/mixed-case-fields.yaml @@ -34,6 +34,9 @@ components: data: type: string format: binary + checksum: + type: string + format: byte metadata: type: object Animal: diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala index 50a03ec77d31..97a592cde9c4 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala @@ -4,6 +4,7 @@ import java.net.{ URI, URISyntaxException } import java.io.File import java.nio.file.Files +import java.util.Base64 trait AdditionalTypeSerializers { import io.circe._ @@ -56,4 +57,10 @@ trait AdditionalTypeSerializers { case "-Infinity" => Right(Double.NegativeInfinity) case s => Left(s"Cannot decode '$s' as Double") }) + + implicit final lazy val FlexibleByteArrayDecoder: Decoder[Array[Byte]] = + Decoder.decodeArray[Byte].or(Decoder.decodeString.emap { s => + try Right(Base64.getDecoder.decode(s)) + catch { case _: IllegalArgumentException => Left(s"Cannot decode '$s' as Base64 Array[Byte]") } + }) } From caffe64f05944a4d931ffcd91e7a9266981a3c88 Mon Sep 17 00:00:00 2001 From: Nikhil Sulegaon Date: Fri, 24 Apr 2026 16:20:25 -0700 Subject: [PATCH 2/2] Rename decoder --- .../openapitools/client/core/AdditionalTypeSerializers.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala index 97a592cde9c4..2844517144b6 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/core/AdditionalTypeSerializers.scala @@ -58,7 +58,7 @@ trait AdditionalTypeSerializers { case s => Left(s"Cannot decode '$s' as Double") }) - implicit final lazy val FlexibleByteArrayDecoder: Decoder[Array[Byte]] = + implicit final lazy val Base64OrArrayByteDecoder: Decoder[Array[Byte]] = Decoder.decodeArray[Byte].or(Decoder.decodeString.emap { s => try Right(Base64.getDecoder.decode(s)) catch { case _: IllegalArgumentException => Left(s"Cannot decode '$s' as Base64 Array[Byte]") }