Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to return bytes but generate spec for specific type + encoding #314

Closed
banool opened this issue Jul 14, 2022 · 15 comments
Closed

How to return bytes but generate spec for specific type + encoding #314

banool opened this issue Jul 14, 2022 · 15 comments
Labels
question Further information is requested Stale

Comments

@banool
Copy link
Contributor

banool commented Jul 14, 2022

First, some code demonstrating what I want to do:

struct Api {
    db_handle: DbHandle
}

#[OpenApi]
impl Api {
    #[oai(path = "/get_stuff", method = "get", actual_type = Base64<Stuff>)]
    async fn get_stuff(&self) -> Binary<Vec<u8>> {
        self.db_handle.get_stuff()
    }
}

In short, I want to be able to read bytes from a DB and return them, but because I know they really represent a type (Stuff) encoded in a particular way (Base64), generate the OpenAPI spec in a way that says it returns Base64 encoded Stuff, not just a bunch of bytes.

Is there a way to do this today in Poem?

I want this to save myself having to deserialise Stuff just to reserialize it again.

Thanks!!

@banool banool added the question Further information is requested label Jul 14, 2022
@sunli829
Copy link
Collaborator

struct Api {
    db_handle: DbHandle
}

#[OpenApi]
impl Api {
    #[oai(path = "/get_stuff", method = "get")]
    async fn get_stuff(&self) -> Base64<Vec<u8>> { // << just change this to Base64<Vec<u8>>
        self.db_handle.get_stuff()
    }
}

@sunli829
Copy link
Collaborator

sunli829 commented Jul 14, 2022

You don't want to use types::Base64 because its format is string(bytes) and you want stuff(base64)?

If so, you might be able to reference this to create a new type?

https://github.com/poem-web/poem/blob/master/poem-openapi/src/types/base64_type.rs

@banool
Copy link
Contributor Author

banool commented Jul 14, 2022

Sorry I don't think I was very clear with what I'm asking here. I didn't mean to include both Binary and Base64 above, just Base64.

Imagine I have code like this:

struct Stuff {
    first_name: String,
    last_name: String,
}

struct Api {
    db_handle: DbHandle
}

#[OpenApi]
impl Api {
    #[oai(path = "/get_stuff", method = "get", schema = Stuff)]
    async fn get_stuff(&self) -> Base64<Vec<u8>> {
        self.db_handle.get_stuff()
    }
}

What I'm trying to declare here is when we generate the OpenAPI spec, it should say that this function returns Stuff:

      responses:
        "200":
          content:
            application/base64:
              schema:
                $ref: '#/components/schemas/Stuff'

But within the code, I don't want to construct Stuff, I want to just pull the bytes from the DB and return them directly (for efficiency) because I know that the DB contains a correct base64 encoding of Stuff already.

So in the above code you see I add this schema thing that tells it what type to use for the spec generation. Is this kind of thing possible?

A more complete code example, missing the schema declaration in oai since it doesn't exist: https://gist.github.com/7a82fbe9845e23165150e13013a492de.

@sunli829
Copy link
Collaborator

I see, you want to return the binary directly from the database, thus avoiding unnecessary serialization, I'll add an actual_type attribute later.

@banool
Copy link
Contributor Author

banool commented Jul 14, 2022

Awesome you're a legend.

@sunli829
Copy link
Collaborator

Is this what you want?

async fn actual_type() {

@banool
Copy link
Contributor Author

banool commented Jul 15, 2022

Yes that's exactly it, this would be very helpful for my use case.

@banool
Copy link
Contributor Author

banool commented Jul 15, 2022

Actually on closer inspection, this doesn't quite do what I want. Apologies, it works for the example I gave, but I actually have a more complicated case:

#[derive(ResponseContent)]
pub enum MyResponseContent<T: ToJSON + Send + Sync> {
    // When returning data as JSON, we take in T and then serialize to JSON
    // as part of the response.
    Json(Json<T>),

    // When returning data as binary, we never actually interact with the Rust
    // type. Instead, we just return the bytes we read from the DB directly, for
    // efficiency reasons. Only through the `schema` decalaration at the
    // endpoints does the return type make its way into the OpenAPI spec.
    // TODO: This `schema` / `actual_type` declaration doesn't exist yet
    #[oai(actual_type = "Binary<T>")]
    Binary(Binary<Vec<u8>>),
}

#[derive(ApiResponse)]
pub enum MyResponse<T: ToJSON + Send + Sync> {
    #[oai(status = 200)]
    Ok(MyResponseContent<T>),
}

I imagine this is more complicated to add.

@banool
Copy link
Contributor Author

banool commented Jul 15, 2022

Upon further experimentation, I can add the following types to make it work!

#[derive(ApiResponse)]
pub enum ResponseDeclaration<T: ToJSON + Send + Sync + Serialize> {
    #[oai(status = 200)]
    Ok(ResponseDeclarationContent<T>),
}

#[derive(ResponseContent)]
pub enum ResponseDeclarationContent<T: ToJSON + Send + Sync + Serialize> {
    Json(Json<T>),
    Binary(Binary<T>),
}

Which I then use like this:

        #[oai(path = "/", method = "get", actual_type = "ResponseDeclaration<MyObj>")]
        async fn test(&self) -> Binary<Vec<u8>> {
            Binary(b"{ \"value\": 100 }".to_vec())
        }

@sunli829
Copy link
Collaborator

#[derive(ResponseContent)]
pub enum MyResponseContent<T: ToJSON + Send + Sync> {
    Json(Json<T>),
    #[oai(actual_type = "Binary<T>")] // <<<< I'll add this
    Binary(Binary<Vec<u8>>),
}

@banool
Copy link
Contributor Author

banool commented Jul 15, 2022

Awesome that would help a lot, because the solution above works but there is no type checking that the inner type is the same.

@banool
Copy link
Contributor Author

banool commented Jul 15, 2022

Once you add it I'll make an example demonstrating it 💯

@sunli829
Copy link
Collaborator

Done! 🙂

@github-actions
Copy link

This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions github-actions bot added the Stale label Aug 15, 2022
@github-actions
Copy link

This issue was closed because it has been stalled for 5 days with no activity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested Stale
Projects
None yet
Development

No branches or pull requests

2 participants