Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/samples-kotlin-echo-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
- samples/client/echo_api/kotlin-jvm-spring-3-restclient
- samples/client/echo_api/kotlin-model-prefix-type-mappings
- samples/client/echo_api/kotlin-jvm-okhttp
- samples/client/echo_api/kotlin-jvm-okhttp-multipart-json
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ samples/client/petstore/kotlin*/build/
samples/server/others/kotlin-server/jaxrs-spec/build/
samples/client/echo_api/kotlin-jvm-spring-3-restclient/build/
samples/client/echo_api/kotlin-jvm-okhttp/build/
samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/build/

# haskell
.stack-work
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ import com.squareup.moshi.adapter
* @see requestBody
*/
protected fun MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, file: File) {
val partHeaders = headers.toMutableMap() +
// Filter out Content-Type from headers as OkHttp requires it to be passed
// separately via asRequestBody(mediaType), not in the headers map
val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
("Content-Disposition" to "form-data; name=\"$name\"; filename=\"${file.name}\"")
val fileMediaType = guessContentTypeFromFile(file).toMediaTypeOrNull()
addPart(
Expand All @@ -148,11 +150,31 @@ import com.squareup.moshi.adapter
* @see requestBody
*/
protected fun <T> MultipartBody.Builder.addPartToMultiPart(name: String, headers: Map<String, String>, obj: T?) {
val partHeaders = headers.toMutableMap() +
val partContentType = headers["Content-Type"]
val partMediaType = partContentType?.toMediaTypeOrNull()
// Filter out Content-Type from headers as OkHttp requires it to be passed
// separately via toRequestBody(mediaType), not in the headers map
val partHeaders = headers.filterKeys { it != "Content-Type" }.toMutableMap() +
("Content-Disposition" to "form-data; name=\"$name\"")
val partBody = if (partContentType?.contains("json") == true) {
{{#moshi}}
Serializer.moshi.adapter(Any::class.java).toJson(obj)
{{/moshi}}
{{#gson}}
Serializer.gson.toJson(obj)
{{/gson}}
{{#jackson}}
Serializer.jacksonObjectMapper.writeValueAsString(obj)
{{/jackson}}
{{#kotlinx_serialization}}
Serializer.kotlinxSerializationJson.encodeToString(obj)
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Outdated
{{/kotlinx_serialization}}
} else {
parameterToString(obj)
}
addPart(
partHeaders.toHeaders(),
parameterToString(obj).toRequestBody(null)
partBody.toRequestBody(partMediaType)
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
openapi: 3.0.0
servers:
- url: 'http://localhost:3000/'
info:
version: 1.0.0
title: Echo API for Kotlin Multipart JSON Test
description: Echo server API to test multipart/form-data with JSON content-type
license:
name: Apache-2.0
url: 'https://www.apache.org/licenses/LICENSE-2.0.html'
tags:
- name: body
description: Test body operations
paths:
/body/multipart/formdata/with_json_part:
post:
tags:
- body
summary: Test multipart with JSON part
description: Test multipart/form-data with a part that has Content-Type application/json
operationId: testBodyMultipartFormdataWithJsonPart
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- metadata
- file
properties:
metadata:
$ref: '#/components/schemas/FileMetadata'
file:
type: string
format: binary
description: File to upload
encoding:
metadata:
contentType: application/json
file:
contentType: image/jpeg
responses:
'200':
description: Successful operation
content:
text/plain:
schema:
type: string
components:
schemas:
FileMetadata:
type: object
required:
- id
- name
properties:
id:
type: integer
format: int64
example: 12345
name:
type: string
example: test-file
tags:
type: array
items:
type: string
example: ["tag1", "tag2"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator

# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.

# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux

# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux

# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
README.md
build.gradle
docs/BodyApi.md
docs/FileMetadata.md
gradle/wrapper/gradle-wrapper.jar
gradle/wrapper/gradle-wrapper.properties
gradlew
gradlew.bat
settings.gradle
src/main/kotlin/org/openapitools/client/apis/BodyApi.kt
src/main/kotlin/org/openapitools/client/infrastructure/ApiAbstractions.kt
src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt
src/main/kotlin/org/openapitools/client/infrastructure/ApiResponse.kt
src/main/kotlin/org/openapitools/client/infrastructure/ByteArrayAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/Errors.kt
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/LocalDateTimeAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/OffsetDateTimeAdapter.kt
src/main/kotlin/org/openapitools/client/infrastructure/PartConfig.kt
src/main/kotlin/org/openapitools/client/infrastructure/RequestConfig.kt
src/main/kotlin/org/openapitools/client/infrastructure/RequestMethod.kt
src/main/kotlin/org/openapitools/client/infrastructure/ResponseExtensions.kt
src/main/kotlin/org/openapitools/client/infrastructure/Serializer.kt
src/main/kotlin/org/openapitools/client/models/FileMetadata.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.20.0-SNAPSHOT
61 changes: 61 additions & 0 deletions samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# org.openapitools.client - Kotlin client library for Echo API for Kotlin Multipart JSON Test

Echo server API to test multipart/form-data with JSON content-type

## Overview
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate an API client.

- API version: 1.0.0
- Package version:
- Generator version: 7.20.0-SNAPSHOT
- Build package: org.openapitools.codegen.languages.KotlinClientCodegen

## Requires

* Kotlin 2.2.20
* Gradle 8.14

## Build

First, create the gradle wrapper script:

```
gradle wrapper
```

Then, run:

```
./gradlew check assemble
```

This runs all tests and packages the library.

## Features/Implementation Notes

* Supports JSON inputs/outputs, File inputs, and Form inputs.
* Supports collection formats for query parameters: csv, tsv, ssv, pipes.
* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in OpenAPI definitions.
* Implementation of ApiClient is intended to reduce method counts, specifically to benefit Android targets.

<a id="documentation-for-api-endpoints"></a>
## Documentation for API Endpoints

All URIs are relative to *http://localhost:3000*

| Class | Method | HTTP request | Description |
| ------------ | ------------- | ------------- | ------------- |
| *BodyApi* | [**testBodyMultipartFormdataWithJsonPart**](docs/BodyApi.md#testbodymultipartformdatawithjsonpart) | **POST** /body/multipart/formdata/with_json_part | Test multipart with JSON part |


<a id="documentation-for-models"></a>
## Documentation for Models

- [org.openapitools.client.models.FileMetadata](docs/FileMetadata.md)


<a id="documentation-for-authorization"></a>
## Documentation for Authorization

Endpoints do not require authorization.

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
group 'org.openapitools'
version '1.0.0'

wrapper {
gradleVersion = '8.14.3'
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}

buildscript {
ext.kotlin_version = '2.2.20'
ext.spotless_version = "7.2.1"

repositories {
maven { url "https://repo1.maven.org/maven2" }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.diffplug.spotless:spotless-plugin-gradle:$spotless_version"
}
}

apply plugin: 'kotlin'
apply plugin: 'maven-publish'
apply plugin: 'com.diffplug.spotless'

repositories {
maven { url "https://repo1.maven.org/maven2" }
}

// Use spotless plugin to automatically format code, remove unused import, etc
// To apply changes directly to the file, run `gradlew spotlessApply`
// Ref: https://github.com/diffplug/spotless/tree/main/plugin-gradle
spotless {
// comment out below to run spotless as part of the `check` task
enforceCheck false

format 'misc', {
// define the files (e.g. '*.gradle', '*.md') to apply `misc` to
target '.gitignore'

// define the steps to apply to those files
trimTrailingWhitespace()
indentWithSpaces() // Takes an integer argument if you don't like 4
endWithNewline()
}
kotlin {
ktfmt()
}
}

test {
useJUnitPlatform()
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "com.google.code.gson:gson:2.13.2"
implementation "com.squareup.okhttp3:okhttp:5.1.0"
testImplementation "io.kotlintest:kotlintest-runner-junit5:3.4.2"
}

java {
withSourcesJar()
}

publishing {
publications {
maven(MavenPublication) {
groupId = 'org.openapitools'
artifactId = 'kotlin-client'
version = '1.0.0'
from components.java
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# BodyApi

All URIs are relative to *http://localhost:3000*

| Method | HTTP request | Description |
| ------------- | ------------- | ------------- |
| [**testBodyMultipartFormdataWithJsonPart**](BodyApi.md#testBodyMultipartFormdataWithJsonPart) | **POST** /body/multipart/formdata/with_json_part | Test multipart with JSON part |


<a id="testBodyMultipartFormdataWithJsonPart"></a>
# **testBodyMultipartFormdataWithJsonPart**
> kotlin.String testBodyMultipartFormdataWithJsonPart(metadata, file)

Test multipart with JSON part

Test multipart/form-data with a part that has Content-Type application/json

### Example
```kotlin
// Import classes:
//import org.openapitools.client.infrastructure.*
//import org.openapitools.client.models.*

val apiInstance = BodyApi()
val metadata : FileMetadata = // FileMetadata |
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Jan 30, 2026

Choose a reason for hiding this comment

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

P3: The Kotlin example declares metadata with an = but provides no value, producing invalid code that users cannot compile or run.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/docs/BodyApi.md, line 25:

<comment>The Kotlin example declares `metadata` with an `=` but provides no value, producing invalid code that users cannot compile or run.</comment>

<file context>
@@ -0,0 +1,57 @@
+//import org.openapitools.client.models.*
+
+val apiInstance = BodyApi()
+val metadata : FileMetadata =  // FileMetadata | 
+val file : java.io.File = BINARY_DATA_HERE // java.io.File | File to upload
+try {
</file context>
Fix with Cubic

val file : java.io.File = BINARY_DATA_HERE // java.io.File | File to upload
try {
val result : kotlin.String = apiInstance.testBodyMultipartFormdataWithJsonPart(metadata, file)
println(result)
} catch (e: ClientException) {
println("4xx response calling BodyApi#testBodyMultipartFormdataWithJsonPart")
e.printStackTrace()
} catch (e: ServerException) {
println("5xx response calling BodyApi#testBodyMultipartFormdataWithJsonPart")
e.printStackTrace()
}
```

### Parameters
| **metadata** | [**FileMetadata**](FileMetadata.md)| | |
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Jan 30, 2026

Choose a reason for hiding this comment

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

P3: Malformed Markdown table: header/separator rows come after the first data row, so metadata is rendered outside the table.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At samples/client/echo_api/kotlin-jvm-okhttp-multipart-json/docs/BodyApi.md, line 40:

<comment>Malformed Markdown table: header/separator rows come after the first data row, so `metadata` is rendered outside the table.</comment>

<file context>
@@ -0,0 +1,57 @@
+```
+
+### Parameters
+| **metadata** | [**FileMetadata**](FileMetadata.md)|  | |
+| Name | Type | Description  | Notes |
+| ------------- | ------------- | ------------- | ------------- |
</file context>
Fix with Cubic

| Name | Type | Description | Notes |
| ------------- | ------------- | ------------- | ------------- |
| **file** | **java.io.File**| File to upload | |

### Return type

**kotlin.String**

### Authorization

No authorization required

### HTTP request headers

- **Content-Type**: multipart/form-data
- **Accept**: text/plain

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

# FileMetadata

## Properties
| Name | Type | Description | Notes |
| ------------ | ------------- | ------------- | ------------- |
| **id** | **kotlin.Long** | | |
| **name** | **kotlin.String** | | |
| **tags** | **kotlin.collections.List&lt;kotlin.String&gt;** | | [optional] |



Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading
Loading