Foso / Ktorfit

HTTP client generator / KSP plugin for Kotlin Multiplatform (Android, iOS, Js, Jvm, Native, WasmJs)) using KSP and Ktor clients inspired by Retrofit https://foso.github.io/Ktorfit
https://foso.github.io/Ktorfit
Apache License 2.0
1.6k stars 43 forks source link

Uploading a file and sending a json object in one POST request #739

Closed padmalcom closed 3 days ago

padmalcom commented 4 days ago

We are using a backend api that allows us to upload a file and posing a json object in one single POST request. Signature looks like that:

    @PostMapping("/api/v1/images")
    fun createImage(
        @RequestPart("data") data: Image,
        @RequestParam("file") file: MultipartFile
    ): Image {
      ...
   }

My approach to serve this endpoint is this api:

   @Multipart
    @POST("/api/v1/images")
    suspend fun createImage(
        @Part("file") file: List<PartData>,
        @Part("data") image: Data
    ): Response<Image>

I get this answer from the backend: HttpMediaTypeNotSupportedException: Content-Type 'application/octet-stream' is not supported

The error is clearly understandable but I have no idea where the octet-steam comes from, if it relates to the file and/or the data part and if it is avoidable.

Since we are migrating from retrofit to ktorfit I can surely confirm that this approach works under retrofit.

I bet this is not a bug but a case that could be documented somewhere. Hope you guys have an idea how posting files and data could work.

padmalcom commented 3 days ago

Okay I could solve it tonight. The client api should look like that:

    @POST(Endpoints.IMAGES)
    suspend fun createImage(
        @Body map: MultiPartFormDataContent
    ): Response<Image>

... and it can be used like that:

            val multipart = MultiPartFormDataContent(formData {
                append("file", fileByteArray, Headers.build {
                    append(HttpHeaders.ContentType, ContentType.Image.Any.toString())
                    append(HttpHeaders.ContentDisposition, "filename=${image.fileName}")
                })
                append("data", Json.encodeToString(Image.serializer(), image), Headers.build {
                    append(HttpHeaders.ContentType, ContentType.Application.Json.toString())
                })
            })

            imageApi.createImage(
                map = multipart
            )
Foso commented 2 days ago

Hi @padmalcom, nice that you found a solution and thank you for sharing it!