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

Trying to use a form with a rocket::fs::Tempfile with okapi #51

Closed
richardmarston opened this issue Jul 14, 2021 · 2 comments
Closed

Trying to use a form with a rocket::fs::Tempfile with okapi #51

richardmarston opened this issue Jul 14, 2021 · 2 comments
Assignees

Comments

@richardmarston
Copy link

Hi,

I'm trying to use okapi to generate openapi.yaml, but I'm having problems because the datatypes that I'm trying to use perhaps aren't supported by okapi, or perhaps that I don't understand the way they are supported. I have ended up implementing a lot of Traits and I'm just wondering if I haven't taken a wrong turn and made it harder than it needs to be.

I want to use an endpoint with a Form that contains a file. This is fairly easy in Rocket using the TempFile struct. When I try to mark the endpoint with openapi, I have errors that JsonSchema, Deserialize, Serialize and Default are not implemented for TempFile. In order to implement the Traits, I have to create a local struct for TempFile because you are not allowed to implement a trait you didn't define for a struct you didn't define. Then you need to write code incorporating the functionality from the original struct into your struct. Below are the stubs that I have implemented to satisfy the compiler for now. In the current state, it complains that OpenApiFromData is not implemented for rocket::form::Form<ExampleForm<'_>>. To solve this it looks like I might have to declare a local struct and add all of the traits and functions to it that I need.

Am I going about this the right way?

Here is my code:

#[macro_use] extern crate rocket;
use rocket::serde::json::Json;
use std::path::Path;
use rocket::form::Form;
use rocket_okapi::{ openapi, routes_with_openapi };
use serde::{Deserialize, Serialize};
use schemars::JsonSchema;
use rocket::fs::TempFile;
use schemars::gen::SchemaGenerator;

///////////// This is the code I want to use //////////////////////////////////////////

#[derive(Serialize, Deserialize, FromForm, JsonSchema)]
pub struct Example {
    example_id:        u64,
}

#[derive(FromForm, JsonSchema, Debug, Default, Serialize, Deserialize)]
pub struct ExampleForm<'f> {
    example_data: ExampleTempFile<'f>,
}

#[openapi]
#[post("/example", format = "multipart/form-data", data = "<form>")]
pub async fn post_example(form: Form < ExampleForm < '_ > > ) -> std::result::Result<Json<Example>, String> {
    let example = Example {
        example_id: 1
    };
    Ok(Json(example))
}

#[rocket::main]
#[doc = "The main entry point for Rocket" ]
async fn main() -> Result <(), rocket::Error> {

    rocket::build()
        .mount("/", routes_with_openapi![post_example])
        .launch()
        .await
}


///////////// This is the supporting code stubs I think I need to fill out //////////

#[derive (Debug)]
struct ExampleTempFile<'a>(&'a mut TempFile<'a>);

impl ExampleTempFile<'_> {
    pub async fn persist_to<P>(&mut self, path: P) -> std::io::Result<()>
    where P: AsRef<Path>
    {
        self.0.persist_to(path).await
    }
}

impl<'de> Deserialize<'de> for  ExampleTempFile<'_> {
    fn deserialize<D>(_: D) -> Result<Self, <D as rocket::serde::Deserializer<'de>>::Error> where D: rocket::serde::Deserializer<'de> {
        Ok(ExampleTempFile { 0: &mut TempFile::Buffered { content: "" } })
    }
}

impl Serialize for ExampleTempFile<'_> {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where S: rocket::serde::Serializer {
        serializer.serialize_bool(true)
    }
}

impl Default for ExampleTempFile<'_> {
    fn default() -> Self {
        ExampleTempFile{ 0: &mut TempFile::default().unwrap() }
    }
}

impl<'a> schemars::JsonSchema for ExampleTempFile<'a> {
    fn schema_name() -> String {
        "TempFile".to_owned()
    }
    fn json_schema( _: &mut SchemaGenerator ) -> schemars::schema::Schema {
        schemars::schema::SchemaObject::default().into()
    }
}

use rocket::form::FromFormField;
impl<'__f> FromFormField<'_> for ExampleTempFile<'__f> {
}

//////////// This is the bit where it looks like I might have to do the same trick with the local struct again //////////////////

use okapi::Map;
use rocket_okapi::{ gen::OpenApiGenerator, request::OpenApiFromData, Result as OpenApiResult };
use okapi::openapi3::{RequestBody};
impl<'r> OpenApiFromData<'r> for Form<ExampleForm<'r>> {
    fn request_body(gen: &mut OpenApiGenerator) -> OpenApiResult <RequestBody>{
        Ok(RequestBody {
            content: { Map::new() },
            required: true,
            ..Default::default()
        })
    }
}

Here is my Cargo.toml:

[package]
name = "example"
version = "0.1.0"
edition = "2018"

[dependencies]
rocket = { version = "0.5.0-rc.1", features = [ "json" ] }
serde = { version = "1.0.64", features = ["derive"] }
serde_json = "1.0.64"
log = "0.4.14"
async-global-executor = "2.0.2"
okapi = "0.6.0-alpha-1"
rocket_okapi = "0.7.0-alpha-1"
schemars = "^0.8"
@ralpha
Copy link
Collaborator

ralpha commented Sep 8, 2021

Yeah I'm currently updating the crate to add better support for Rocket 0.5.
I also noticed that TempFile was not implemented.
Because okapi depend on Schema for the JsonSchema implementation I created an issue there: GREsau/schemars#103

For now there is not really an easy way to do this. So your code might be the best you can do for now until it is fully implemented.

@ralpha
Copy link
Collaborator

ralpha commented Sep 9, 2021

I implemented TempFile in all the places where allowed and I can get it to work.
But for the JsonSchema impl this need to be done in Schemars.
Once this issue is solved in Schemars it will automatically work in okapi (the next version, because of generics)

So I'll close this issue here as there is nothing more to do in this crate.

@ralpha ralpha closed this as completed Sep 9, 2021
@ralpha ralpha self-assigned this Sep 9, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants