juhaku/utoipa

Returning Binary Data is hard in utoipa 5

Opened this issue · 3 comments

Previously, I could use [u8] in the path macro responses to generate openapi for returning a binary blob.
Now, that is pretty much impossible inside the responses field. The only way I could manage to do it is to define a struct just for use in utoipa.

/// Unused dummy struct only used for an OpenAPI definition.
#[derive(ToSchema)]
#[schema(value_type = String, format = Binary)]
struct BinaryFile(PhantomData<Vec<u8>>);

#[utoipa::path(
    get,
    path = "/download",
    responses(
        (status = 200, description = "", content_type = "application/octet-stream", body=inline(BinaryFile))
    )
)]

The generated OpenAPI should look like the below snippet. Openapi-generator requires the "schema" field, otherwise no body is expected.

"content": {
    "application/octet-stream": {
      "schema": {
        "type": "string",
        "format": "binary"
      }
    }
  }

The actual body is a kind of stream with custom headers, which does not implement ToSchema.

Yeah, that is changed behavior from OpenAPI 3.0
https://docs.rs/utoipa/latest/utoipa/attr.path.html#defining-file-uploads, https://spec.openapis.org/oas/v3.1.0.html#considerations-for-file-uploads and this https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0

The file uploads has changed a bit in OpenAPI 3.1. Like if you return the [u8] array, the OpenAPI 3.0 way of defining it as type: string format: binary is incorrect, as the value is actually an array of bytes hence type: array items: type: number.

But that is true, if you explicitly want to define it as type: string and format: binary then the new type is needed for it.

I do not have an issue generating either supposedly OpenAPI 3.1 or 3.0 definitions for file uploads. It's just that generating the "old" way now requires an additional struct just for an API definition that is never used.

The actual type I am returning is an axum Body created using Body::from_stream using a file as the source. Obviously this type does not have utoipa traits implemented. It's a bit annoying to assume all endpoints are consuming or returning large files by fully loading them into memory using Vec.

Then of course it's also a question of tooling. While the below definition should be correct for 3.1, openapi-generator still generates incorrect code. I know this is not an utoipa issue, but for compatibility reasons could be nice to support specifying the "format" manually.

OpenAPI 3.1:

# an arbitrary binary file:
content:
    application/octet-stream: {}

Sure, I need to see what can be done to improve the file uploads. Also you can use utoipa-config to define alias for Body as Vec<u8> which then would then make the Body behave as it was Vec of bytes. Though this does not actually remove the need for the format Binary for the old OpenAPI 3.0.