apple / swift-openapi-generator

Generate Swift client and server code from an OpenAPI document.
https://swiftpackageindex.com/apple/swift-openapi-generator/documentation
Apache License 2.0
1.21k stars 87 forks source link

How to send a file in multipart/form-data? #540

Open yousifalraheem opened 2 months ago

yousifalraheem commented 2 months ago

Question

I'm using your package to generate my APIs which has been working great for me. However, as soon as I started working with uploading files, I stumbled upon an issue with multipart/form-data. Whenever I use the generated API, it sends the request with the file having a mimetype of application/octet-stream. Which is something that I am not supporting on the backend nor do I have plans to do so. If I'm uploading a jpeg image, I expect the mimetype to be image/jpeg. How do I get this desired behavior?

This is how I create data from a UIImage:

func imageToMultipartData(_ image: UIImage) async throws -> Data {
  let boundary = UUID().uuidString

  var data = Data()
  data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
  data.append("Content-Disposition: form-data; name=\"file\"; filename=\"image.jpeg\"\r\n".data(using: .utf8)!)
  data.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
  data.append(image.jpegData(compressionQuality: 1.0)!)
  data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)

  return data
}

This is how I'm using the generated API:

guard let image = viewModel.selectedImage else {
  throw URLError(.cannotDecodeRawData)
}
let data = try await imageToMultipartData(image)
let response = try await HttpClient.updateProfileImage(
  body: .multipartForm([
    .file(.init(
      payload: .init(body: HTTPBody(data)),
      filename: "file.jpeg"
    ))
  ])
)

This is my schema for the method:

/users/profile/image:
  post:
    operationId: updateProfileImage
    parameters: []
    requestBody:
      required: true
      content:
        multipart/form-data:
          schema:
            $ref: '#/components/schemas/UploadImageDto'
    responses:
      '201':
        description: ''
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UserProfileDto'
      '400':
        description: Bad request
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ValidationErrorDto'

Where UploadImageDto component have this schema:

UploadImageDto:
  type: object
  properties:
    file:
      type: string
      format: binary
  required:
    - file
simonjbeaumont commented 2 months ago

Hey @yousifalraheem. We have an example package for client and server that show how to handle various request and response content types, including multipart here: https://github.com/apple/swift-openapi-generator/tree/main/Examples/various-content-types-client-example.

If that doesn't help, let us know and we can take a closer look at your use case specifically.

czechboy0 commented 2 months ago

Hi @yousifalraheem,

it seems you're manually serializing the multipart part, but that's not correct - the generated code already does that. You need to simplify your Data creation to just:

func imageToMultipartData(_ image: UIImage) async throws -> Data {
  image.jpegData(compressionQuality: 1.0)!
}
czechboy0 commented 1 month ago

Hi @yousifalraheem - were you able to get it working?