Skip to content

fix(kotlin-client): actually send the jvm-vertx form body - #25623

Open
khayashi4337 wants to merge 6 commits into
OpenAPITools:masterfrom
khayashi4337:fix/kotlin-vertx-send-form-body
Open

khayashi4337 wants to merge 6 commits into
OpenAPITools:masterfrom
khayashi4337:fix/kotlin-vertx-send-form-body

Conversation

@khayashi4337

@khayashi4337 khayashi4337 commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

The kotlin client with library=jvm-vertx builds a form for operations with form parameters but never sends it, so every application/x-www-form-urlencoded and multipart/form-data request goes out with an empty body.

Reproducer

Any operation with form parameters, e.g. updatePetWithForm and uploadFile in the petstore samples. On master, samples/client/petstore/kotlin-jvm-vertx-jackson/.../PetApi.kt:

request.putHeader("Content-Type", "multipart/form-data")
...
val form = io.vertx.core.MultiMap.caseInsensitiveMultiMap();
additionalMetadata?.let { form.add("additionalMetadata", additionalMetadata) }
file?.let { form.add("file", file.toString()) }
...
return request
    .send()

The form is populated and then dropped: .send() sends no body. In the multipart case the file part would also be wrong even if it were sent, because file.toString() is the file's path, not its content. Array form parameters are added as a single toString() of the whole list.

Fix

In kotlin-client/libraries/jvm-vertx/api.mustache:

  • urlencoded operations keep the MultiMap and send it with .sendForm(form).
  • multipart operations build an io.vertx.ext.web.multipart.MultipartForm and send it with .sendMultipartForm(form).
    • file parameters use form.binaryFileUpload(baseName, it.name, it.absolutePath, contentType). With mapFileBinaryToByteArray=true the parameter is a ByteArray, so the bytes are sent through the Buffer overload instead: form.binaryFileUpload(baseName, baseName, Buffer.buffer(it), contentType). contentType is the part's encoding contentType when the spec sets one, otherwise application/octet-stream.
    • other parameters use form.attribute(baseName, value).
  • array form parameters go through the client's existing toMultiValue(list, collectionFormat) helper: form.add(name, values) for urlencoded and one form.attribute(name, value) per returned value for multipart. toMultiValue returns one value per item only for collectionFormat multi. For other formats, including the csv that the generator assigns to OAS 3 form arrays without an encoding entry, it returns one joined value (tags=a,b). That default is existing generator behavior and is unchanged here. For arrays of enums, each item's wire value is passed to toMultiValue, as the single-enum case already does.
  • form operations no longer call request.putHeader("Content-Type", …). sendForm and sendMultipartForm set the header themselves. With a preset header, Vert.x 5.1.5 (the version in the samples' build.gradle) throws IllegalStateException when the media type is anything other than exactly application/x-www-form-urlencoded or multipart/form-data, e.g. application/x-www-form-urlencoded; charset=UTF-8. A multipart operation is therefore always sent as multipart/form-data.
  • operations without form parameters still use .send(), and operations with a body parameter still use .sendBuffer(...). Neither path changes.

After regeneration, the multipart operation in the sample reads:

val form = io.vertx.ext.web.multipart.MultipartForm.create()
additionalMetadata?.let { form.attribute("additionalMetadata", additionalMetadata) }
file?.let { form.binaryFileUpload("file", it.name, it.absolutePath, "application/octet-stream") }
...
return request
    .sendMultipartForm(form)

Tests

modules/openapi-generator/src/test/resources/3_0/kotlin/kotlin-vertx-form-send.yaml (added by this PR) has a urlencoded operation with an array field tags and an enum array field, a second urlencoded operation declared as application/x-www-form-urlencoded; charset=UTF-8, and a multipart operation with a file myFile (whose encoding declares contentType: image/jpeg) and an array field labels.

Test Asserts
testJvmVertxFormBodyIsSent (new) inside each generated operation: the urlencoded one ends with .sendForm(form) and the multipart one with .sendMultipartForm(form); form.binaryFileUpload("myFile" with "image/jpeg" and no form.add("myFile"; no putHeader("Content-Type" anywhere in the file; form.add("tags", toMultiValue(it.toList(), "csv")); the complete toMultiValue(…).forEach { form.attribute("labels", it) } expression; enum array items sent as .value
testJvmVertxFormFileByteArray (new) with mapFileBinaryToByteArray=true, the file part is sent with Buffer.buffer(it)

./mvnw -nsu -Denforcer.skip=true -pl modules/openapi-generator -am -Dtest=KotlinClientCodegenApiTest -Dsurefire.failIfNoSpecifiedTests=false test

Tests run Result
current master merged + this PR 36 0 failures, 0 skipped
this PR with the api.mustache change reverted (tests kept; each new test run on its own) — testJvmVertxFormBodyIsSent and testJvmVertxFormFileByteArray fail

Samples

12 files changed in kotlin-jvm-vertx-gson, kotlin-jvm-vertx-jackson, kotlin-jvm-vertx-jackson-coroutines and kotlin-jvm-vertx-moshi:

  • PetApi.kt: the two form operations now send their form, and their putHeader("Content-Type", …) lines are gone.
  • PetApi.kt, StoreApi.kt, UserApi.kt: operations without form parameters lose an empty line that the removed template line used to leave. This is whitespace only.

All four directories are compiled by samples-kotlin-client.yaml. The petstore samples have no array form parameter, so the array branches are covered by the generator test above.

This PR and its sibling PRs #25622, #25624 and #25625 ($ escaping in wire names) all touch KotlinClientCodegenApiTest and neighboring kotlin-client templates; whichever merges first, I will rebase the others.

cc @wing328 @karismann @Zomzog @andrewemery @4brunu @yutaka0m @stefankoppier @e5l (Kotlin technical committee)

PR checklist

  • Read the contribution guidelines.
  • Run the following to build the project and update samples — scoped to this change: built the CLI (./mvnw -pl modules/openapi-generator-cli -am package -DskipTests) and regenerated the jvm-vertx configs with ./bin/generate-samples.sh; the resulting diff is committed. export_docs_generators.sh not run: no generator option or metadata changes.
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
    Commit all changed files.
    This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master.
    These must match the expectations made by your contribution.
    You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example ./bin/generate-samples.sh bin/configs/java*.
    IMPORTANT: Do NOT purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

🤖 Generated with Claude Code

khayashi4337 and others added 3 commits November 11, 2026 12:25
jvm-vertx populated a `form` MultiMap for form params but dispatched the
request via `.send()` only -- the form body was silently dropped, so every
urlencoded/multipart operation reached the wire with an empty body.

Send it: `.sendForm(form)` for urlencoded forms and
`.sendMultipartForm(form)` with a `MultipartForm` for multipart.

While here, encode multipart correctly: file params went through
`form.add("name", file.toString())` which sends the file's path string,
not its content -- they now use `binaryFileUpload(name, it.name,
it.absolutePath, contentType)`. Array form params used a single
toString() blob -- they now expand via `toMultiValue(...)` (attribute
entries for multipart, a MultiMap add for urlencoded).

Fixture kotlin-vertx-form-send.yaml + a test asserting the form reaches
the send call for both content types.

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The vertx form body is now actually sent: sendForm for urlencoded and
sendMultipartForm + binaryFileUpload for multipart (file content instead
of its path string).

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… jvm-vertx

sendForm()/sendMultipartForm() set Content-Type themselves; emitting
putHeader("Content-Type", ...) on a form operation makes Vert.x 5.1.5
throw IllegalStateException for media types with parameters such as
"application/x-www-form-urlencoded; charset=UTF-8".

- remove the manual Content-Type putHeader for form operations
- drop the invalid OAS2 "consumes:" key from the OAS3 fixture
- add a charset-parametrized urlencoded operation to the fixture
- assert array serialization in both form flavors
  (toMultiValue/collectionFormat entries for urlencoded,
  form.attribute for multipart)
- regenerate kotlin-jvm-vertx-* samples (12 files, all deletions)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 15 files

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

khayashi4337 and others added 3 commits November 11, 2026 17:59
…send-form-body

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alues in jvm-vertx

- with mapFileBinaryToByteArray the file parameter arrives as a
  ByteArray, which has no `name`/`absolutePath`; send the bytes through
  the Buffer-based binaryFileUpload overload so the generated code
  compiles in that configuration
- form arrays of enums now put each item's wire `value` into
  toMultiValue, matching the single-enum handling (previously the enum
  constant's toString went on the wire)
- tighten the form tests: sendForm/sendMultipartForm are asserted inside
  each generated operation, the encoding-declared contentType must reach
  binaryFileUpload, and a ByteArray upload variant is covered

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…send-form-body

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant