diff --git a/modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache b/modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache index d26af861beaa..0b30765000ab 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp4/model.mustache @@ -51,7 +51,7 @@ object {{classname}} { import io.circe.syntax._ import io.circe.generic.semiauto._ - implicit val encoder: Encoder[{{classname}}] = deriveEncoder + implicit val encoder: Encoder[{{classname}}] = deriveEncoder[{{classname}}].mapJson(_.dropNullValues) implicit val decoder: Decoder[{{classname}}] = deriveDecoder } {{/circe}} @@ -180,7 +180,7 @@ object {{classname}} { import io.circe.{Encoder, Decoder} import io.circe.generic.semiauto._ - implicit val encoder: Encoder[{{classname}}] = deriveEncoder + implicit val encoder: Encoder[{{classname}}] = deriveEncoder[{{classname}}].mapJson(_.dropNullValues) implicit val decoder: Decoder[{{classname}}] = deriveDecoder {{/vendorExtensions.x-use-discr}} {{#vendorExtensions.x-use-discr}} @@ -198,7 +198,7 @@ object {{classname}} { case other => sys.error(s"Invalid {{classname}} discriminant: ${other}") } ) - implicit val encoder: Encoder[{{classname}}] = deriveConfiguredEncoder + implicit val encoder: Encoder[{{classname}}] = deriveConfiguredEncoder[{{classname}}].mapJson(_.dropNullValues) implicit val decoder: Decoder[{{classname}}] = deriveConfiguredDecoder {{/vendorExtensions.x-use-discr}} {{/vendorExtensions.x-hasWrappedOneOfMembers}} @@ -326,7 +326,7 @@ object {{classname}} { import io.circe.syntax._ import io.circe.generic.semiauto._ - implicit val encoder: Encoder[{{classname}}] = deriveEncoder + implicit val encoder: Encoder[{{classname}}] = deriveEncoder[{{classname}}].mapJson(_.dropNullValues) implicit val decoder: Decoder[{{classname}}] = deriveDecoder } {{/circe}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/Sttp4CodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/Sttp4CodegenTest.java index 9b44ed0a3019..2c67fddd3a5e 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/Sttp4CodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/Sttp4CodegenTest.java @@ -333,4 +333,37 @@ public void verifyDateTimeLocalGeneratesLocalDateTime() throws IOException { assertFileContains(modelPath, "createdAt: OffsetDateTime"); assertFileContains(modelPath, "updatedAt: Option[OffsetDateTime]"); } + + @Test + public void verifyOptionalFieldsOmittedWhenNone() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + + OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/3_0/scala/sttp4-optional-fields.yaml", null, new ParseOptions()).getOpenAPI(); + + ScalaSttp4ClientCodegen codegen = new ScalaSttp4ClientCodegen(); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put("jsonLibrary", "circe"); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + DefaultGenerator generator = new DefaultGenerator(); + + generator.setGeneratorPropertyDefault(CodegenConstants.MODELS, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false"); + generator.opts(input).generate(); + + // Optional fields set to None must be omitted from the JSON (sttp3 parity), + // not serialized as null: strict servers reject explicit null for + // non-nullable optional properties. + Path petPath = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/model/Pet.scala"); + assertFileContains(petPath, "implicit val encoder: Encoder[Pet] = deriveEncoder[Pet].mapJson(_.dropNullValues)"); + } } diff --git a/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-optional-fields.yaml b/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-optional-fields.yaml new file mode 100644 index 000000000000..006d43da02e9 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/scala/sttp4-optional-fields.yaml @@ -0,0 +1,32 @@ +openapi: 3.0.3 +info: + title: Optional field test + version: 1.0.0 +paths: + /pets: + post: + operationId: createPet + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' +components: + schemas: + Pet: + type: object + required: + - name + properties: + name: + type: string + tag: + type: string diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/ApiResponse.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/ApiResponse.scala index b1b1c90495a6..d1a0a16f99f4 100644 --- a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/ApiResponse.scala +++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/ApiResponse.scala @@ -26,6 +26,6 @@ object ApiResponse { import io.circe.syntax._ import io.circe.generic.semiauto._ - implicit val encoder: Encoder[ApiResponse] = deriveEncoder + implicit val encoder: Encoder[ApiResponse] = deriveEncoder[ApiResponse].mapJson(_.dropNullValues) implicit val decoder: Decoder[ApiResponse] = deriveDecoder } diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Category.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Category.scala index def67cc526f2..546dacdbb753 100644 --- a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Category.scala +++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Category.scala @@ -25,6 +25,6 @@ object Category { import io.circe.syntax._ import io.circe.generic.semiauto._ - implicit val encoder: Encoder[Category] = deriveEncoder + implicit val encoder: Encoder[Category] = deriveEncoder[Category].mapJson(_.dropNullValues) implicit val decoder: Decoder[Category] = deriveDecoder } diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Order.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Order.scala index 6cb20a7a07cd..4539ecfa9cf5 100644 --- a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Order.scala +++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Order.scala @@ -31,7 +31,7 @@ object Order { import io.circe.syntax._ import io.circe.generic.semiauto._ - implicit val encoder: Encoder[Order] = deriveEncoder + implicit val encoder: Encoder[Order] = deriveEncoder[Order].mapJson(_.dropNullValues) implicit val decoder: Decoder[Order] = deriveDecoder } object OrderEnums { diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Pet.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Pet.scala index 88f0ce279b25..2368e1fb7543 100644 --- a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Pet.scala +++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Pet.scala @@ -30,7 +30,7 @@ object Pet { import io.circe.syntax._ import io.circe.generic.semiauto._ - implicit val encoder: Encoder[Pet] = deriveEncoder + implicit val encoder: Encoder[Pet] = deriveEncoder[Pet].mapJson(_.dropNullValues) implicit val decoder: Decoder[Pet] = deriveDecoder } object PetEnums { diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Tag.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Tag.scala index 4672a3316f36..6840fc050e20 100644 --- a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Tag.scala +++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/Tag.scala @@ -25,6 +25,6 @@ object Tag { import io.circe.syntax._ import io.circe.generic.semiauto._ - implicit val encoder: Encoder[Tag] = deriveEncoder + implicit val encoder: Encoder[Tag] = deriveEncoder[Tag].mapJson(_.dropNullValues) implicit val decoder: Decoder[Tag] = deriveDecoder } diff --git a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/User.scala b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/User.scala index f488d72e97d1..1f42e16a497a 100644 --- a/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/User.scala +++ b/samples/client/petstore/scala-sttp4-circe/src/main/scala/org/openapitools/client/model/User.scala @@ -32,6 +32,6 @@ object User { import io.circe.syntax._ import io.circe.generic.semiauto._ - implicit val encoder: Encoder[User] = deriveEncoder + implicit val encoder: Encoder[User] = deriveEncoder[User].mapJson(_.dropNullValues) implicit val decoder: Decoder[User] = deriveDecoder }