From 9b498042000144d0e7432ff32279db6f950f2f4d Mon Sep 17 00:00:00 2001 From: Mattias-Sehlstedt <60173714+Mattias-Sehlstedt@users.noreply.github.com> Date: Mon, 21 Jul 2025 18:16:39 +0200 Subject: [PATCH 1/3] Adjust header serialization for Optional values --- .../scala-sttp/paramCreation.mustache | 2 +- .../codegen/scala/SttpCodegenTest.java | 34 ++++++++++++++ .../src/test/resources/bugs/issue_21602.yaml | 44 +++++++++++++++++++ .../org/openapitools/client/api/PetApi.scala | 2 +- .../org/openapitools/client/api/PetApi.scala | 2 +- 5 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 modules/openapi-generator/src/test/resources/bugs/issue_21602.yaml diff --git a/modules/openapi-generator/src/main/resources/scala-sttp/paramCreation.mustache b/modules/openapi-generator/src/main/resources/scala-sttp/paramCreation.mustache index 25ec73e8d5e1..a25369ef93eb 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp/paramCreation.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp/paramCreation.mustache @@ -1 +1 @@ -"{{baseName}}", {{#isContainer}}ArrayValues({{{paramName}}}{{#collectionFormat}}, {{collectionFormat.toUpperCase}}{{/collectionFormat}}){{/isContainer}}{{^isContainer}}{{{paramName}}}{{/isContainer}}.toString \ No newline at end of file +"{{baseName}}", {{#isContainer}}ArrayValues({{{paramName}}}{{#collectionFormat}}, {{collectionFormat.toUpperCase}}{{/collectionFormat}}){{/isContainer}}{{^isContainer}}{{{paramName}}}{{/isContainer}}{{#required}}.toString{{/required}}{{^required}}.map(_.toString()).getOrElse(null){{/required}} \ No newline at end of file diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java index 7b745461cf85..b41d1da1f0bd 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java @@ -110,4 +110,38 @@ public void verifyApiKeyLocations() throws IOException { assertFileContains(path, ".cookie(\"apikey\", apiKeyCookie)"); } + @Test + public void headerSerialization() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + + OpenAPI openAPI = new OpenAPIParser() + .readLocation("src/test/resources/bugs/issue_21602.yaml", null, new ParseOptions()).getOpenAPI(); + + ScalaSttpClientCodegen codegen = new ScalaSttpClientCodegen(); + codegen.setOutputDir(output.getAbsolutePath()); + codegen.additionalProperties().put(CXFServerFeatures.LOAD_TEST_DATA_FROM_FILE, "true"); + + 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, "true"); + generator.setGeneratorPropertyDefault(CodegenConstants.SUPPORTING_FILES, "false"); + generator.opts(input).generate(); + + Path path = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/api/DefaultApi.scala"); + assertFileContains(path, ".method(Method.GET, uri\"$baseUrl/ping\")\n"); + assertFileContains(path, ".header(\"X-Optional-Header\", xOptionalHeader.map(_.toString()).getOrElse(null))"); + assertFileContains(path, ".header(\"X-Required-Header\", xRequiredHeader.toString)"); + assertFileContains(path, ".header(\"X-Optional-Schema-Header\", xOptionalSchemaHeader.map(_.toString()).getOrElse(null))"); + assertFileContains(path, ".header(\"X-Required-Schema-Header\", xRequiredSchemaHeader.toString)"); + } + } diff --git a/modules/openapi-generator/src/test/resources/bugs/issue_21602.yaml b/modules/openapi-generator/src/test/resources/bugs/issue_21602.yaml new file mode 100644 index 000000000000..928b5603abe2 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/bugs/issue_21602.yaml @@ -0,0 +1,44 @@ +openapi: "3.0.0" +info: + title: Optional Header Test + version: 1.0.0 +paths: + /ping: + get: + summary: Ping with optional header + operationId: getPing + parameters: + - name: X-Optional-Header + in: header + required: false + schema: + type: string + - name: X-Required-Header + in: header + required: true + schema: + type: string + - name: X-Optional-Schema-Header + in: header + required: false + schema: + $ref: '#/components/schemas/UUID' + - name: X-Required-Schema-Header + in: header + required: true + schema: + $ref: '#/components/schemas/UUID' + responses: + '200': + description: Success + content: + application/json: + schema: + type: string +components: + schemas: + UUID: + type: object + properties: + uuid: + type: string \ No newline at end of file diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/api/PetApi.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/api/PetApi.scala index dc45f1f8c72d..b33b5a749c95 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/api/PetApi.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/api/PetApi.scala @@ -55,7 +55,7 @@ class PetApi(baseUrl: String) { basicRequest .method(Method.DELETE, uri"$baseUrl/pet/${petId}") .contentType("application/json") - .header("api_key", apiKey.toString) + .header("api_key", apiKey.map(_.toString()).getOrElse(null)) .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(())))) /** diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala index dc45f1f8c72d..b33b5a749c95 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala @@ -55,7 +55,7 @@ class PetApi(baseUrl: String) { basicRequest .method(Method.DELETE, uri"$baseUrl/pet/${petId}") .contentType("application/json") - .header("api_key", apiKey.toString) + .header("api_key", apiKey.map(_.toString()).getOrElse(null)) .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(())))) /** From 1770d10be1a0da12e53afa480e8788b7efee42be Mon Sep 17 00:00:00 2001 From: Mattias-Sehlstedt <60173714+Mattias-Sehlstedt@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:54:05 +0200 Subject: [PATCH 2/3] Add additional test verification to display the header type --- .../java/org/openapitools/codegen/scala/SttpCodegenTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java index b41d1da1f0bd..686903f81806 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java @@ -138,9 +138,13 @@ public void headerSerialization() throws IOException { Path path = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/api/DefaultApi.scala"); assertFileContains(path, ".method(Method.GET, uri\"$baseUrl/ping\")\n"); + assertFileContains(path, "xOptionalHeader: Option[String] = None"); assertFileContains(path, ".header(\"X-Optional-Header\", xOptionalHeader.map(_.toString()).getOrElse(null))"); + assertFileContains(path, "xRequiredHeader: String"); assertFileContains(path, ".header(\"X-Required-Header\", xRequiredHeader.toString)"); + assertFileContains(path, "xOptionalSchemaHeader: Option[UUID] = None"); assertFileContains(path, ".header(\"X-Optional-Schema-Header\", xOptionalSchemaHeader.map(_.toString()).getOrElse(null))"); + assertFileContains(path, "xRequiredSchemaHeader: UUID"); assertFileContains(path, ".header(\"X-Required-Schema-Header\", xRequiredSchemaHeader.toString)"); } From 92dae53a5bab7c6ec5cc11b5cae11d480cb4e40d Mon Sep 17 00:00:00 2001 From: Mattias-Sehlstedt <60173714+Mattias-Sehlstedt@users.noreply.github.com> Date: Thu, 24 Jul 2025 11:02:10 +0200 Subject: [PATCH 3/3] Do not get a null value from an option if it is empty, but rather keep the None value. If a value is present it is converted to an Option[String] --- .../src/main/resources/scala-sttp/paramCreation.mustache | 2 +- .../java/org/openapitools/codegen/scala/SttpCodegenTest.java | 4 ++-- .../src/main/scala/org/openapitools/client/api/PetApi.scala | 2 +- .../src/main/scala/org/openapitools/client/api/PetApi.scala | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/scala-sttp/paramCreation.mustache b/modules/openapi-generator/src/main/resources/scala-sttp/paramCreation.mustache index a25369ef93eb..a0c90180947b 100644 --- a/modules/openapi-generator/src/main/resources/scala-sttp/paramCreation.mustache +++ b/modules/openapi-generator/src/main/resources/scala-sttp/paramCreation.mustache @@ -1 +1 @@ -"{{baseName}}", {{#isContainer}}ArrayValues({{{paramName}}}{{#collectionFormat}}, {{collectionFormat.toUpperCase}}{{/collectionFormat}}){{/isContainer}}{{^isContainer}}{{{paramName}}}{{/isContainer}}{{#required}}.toString{{/required}}{{^required}}.map(_.toString()).getOrElse(null){{/required}} \ No newline at end of file +"{{baseName}}", {{#isContainer}}ArrayValues({{{paramName}}}{{#collectionFormat}}, {{collectionFormat.toUpperCase}}{{/collectionFormat}}){{/isContainer}}{{^isContainer}}{{{paramName}}}{{/isContainer}}{{#required}}.toString{{/required}}{{^required}}.map(_.toString()){{/required}} \ No newline at end of file diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java index 686903f81806..c6f2150321bd 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/scala/SttpCodegenTest.java @@ -139,11 +139,11 @@ public void headerSerialization() throws IOException { Path path = Paths.get(outputPath + "/src/main/scala/org/openapitools/client/api/DefaultApi.scala"); assertFileContains(path, ".method(Method.GET, uri\"$baseUrl/ping\")\n"); assertFileContains(path, "xOptionalHeader: Option[String] = None"); - assertFileContains(path, ".header(\"X-Optional-Header\", xOptionalHeader.map(_.toString()).getOrElse(null))"); + assertFileContains(path, ".header(\"X-Optional-Header\", xOptionalHeader.map(_.toString()))"); assertFileContains(path, "xRequiredHeader: String"); assertFileContains(path, ".header(\"X-Required-Header\", xRequiredHeader.toString)"); assertFileContains(path, "xOptionalSchemaHeader: Option[UUID] = None"); - assertFileContains(path, ".header(\"X-Optional-Schema-Header\", xOptionalSchemaHeader.map(_.toString()).getOrElse(null))"); + assertFileContains(path, ".header(\"X-Optional-Schema-Header\", xOptionalSchemaHeader.map(_.toString()))"); assertFileContains(path, "xRequiredSchemaHeader: UUID"); assertFileContains(path, ".header(\"X-Required-Schema-Header\", xRequiredSchemaHeader.toString)"); } diff --git a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/api/PetApi.scala b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/api/PetApi.scala index b33b5a749c95..663261959b93 100644 --- a/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/api/PetApi.scala +++ b/samples/client/petstore/scala-sttp-circe/src/main/scala/org/openapitools/client/api/PetApi.scala @@ -55,7 +55,7 @@ class PetApi(baseUrl: String) { basicRequest .method(Method.DELETE, uri"$baseUrl/pet/${petId}") .contentType("application/json") - .header("api_key", apiKey.map(_.toString()).getOrElse(null)) + .header("api_key", apiKey.map(_.toString())) .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(())))) /** diff --git a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala index b33b5a749c95..663261959b93 100644 --- a/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala +++ b/samples/client/petstore/scala-sttp/src/main/scala/org/openapitools/client/api/PetApi.scala @@ -55,7 +55,7 @@ class PetApi(baseUrl: String) { basicRequest .method(Method.DELETE, uri"$baseUrl/pet/${petId}") .contentType("application/json") - .header("api_key", apiKey.map(_.toString()).getOrElse(null)) + .header("api_key", apiKey.map(_.toString())) .response(asString.mapWithMetadata(ResponseAs.deserializeRightWithError(_ => Right(())))) /**