Input openapi.json/yaml, output one server code in rust.
Online demo with wasm: https://lzpel.github.io/mandolin/
Generate one complete code for server from openapi specification and jinja2 templates.
You can implement actual behavior of api handler with generated types and traits.
Currently, mandolin provide 1 builtin templates for following frameworks
- axum(Rust) https://github.com/tokio-rs/axum
Install mandolin
$ cargo install mandolinRender axum server code using builtin "RUST_AXUM" template
$ mandolin -i ./openapi/openapi_plant.yaml -t RUST_AXUM -o ./examples/readme_axum_generate_out.rsRender axum server code using builtin "RUST_AXUM" template
use mandolin;
use serde_yaml;
fn main() {
// read openapi.yaml
let input_openapi_path = std::env::args()
.nth(1)
.unwrap_or_else(|| "./openapi/openapi_sarod.yaml".to_string());
let input_string = std::fs::read_to_string(input_openapi_path).unwrap();
let input_api = serde_yaml::from_str(&input_string.as_str()).unwrap();
// make environment
let env = mandolin::environment(input_api).unwrap();
// write the rendered output
let output = env.get_template("RUST_AXUM").unwrap().render(0).unwrap();
std::fs::write("examples/readme_axum_generate_out.rs", output).unwrap();
}
// This is generated by mandolin https://github.com/lzpel/mandolin from OpenApi specification
/* Cargo.toml for build this server
[dependencies]
serde= { version="*", features = ["derive"] }
serde_json= "*"
axum = { version = "*", features = ["multipart"] }
tokio = { version = "*", features = ["rt", "rt-multi-thread", "macros", "signal"] }
# optional
uuid = { version = "*", features = ["serde"] }
chrono = { version = "*", features = ["serde"] }
*/
use std::collections::HashMap;
use serde;
use std::future::Future;
pub trait ApiInterface{
fn authorize(&self, _req: axum::http::Request<axum::body::Body>) -> impl Future<Output = Result<AuthContext, String>> + Send{async { Ok(Default::default()) } }
// post /auth
fn authapi_email(&self, _req: AuthapiEmailRequest) -> impl Future<Output = AuthapiEmailResponse> + Send{async{Default::default()}}
// get /auth/callback_oauth
fn authapi_callback_oauth(&self, _req: AuthapiCallbackOauthRequest) -> impl Future<Output = AuthapiCallbackOauthResponse> + Send{async{Default::default()}}
──────────────────────────────────────── 546 lines omitted ────────────────────────────────────────
.map(|s| s.to_ascii_lowercase())
.filter(|s| !s.is_empty());
return Some(mk_origin(proto, host));
}
// 3) Host fallback
let host = headers
.get(axum::http::header::HOST)
.and_then(|h| h.to_str().ok())
.map(str::trim)
.filter(|s| !s.is_empty())?
.to_string();
Some(format!("{}://{}", guess_scheme(&host), host))
}
#[tokio::main]
async fn main() {
let port:u16 = std::env::var("PORT").unwrap_or("8080".to_string()).parse().expect("PORT should be integer");
print_axum_router(port);
let api = TestServer{};
let app = axum_router(api).layer(axum::extract::DefaultBodyLimit::disable());
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}")).await.unwrap();
axum::serve(listener, app)
.with_graceful_shutdown(async { tokio::signal::ctrl_c().await.unwrap() })
.await
.unwrap();
}You can run the mock server from fn main.
You can add your implementation along with the generated trait ApiInterface like followings.
use crate::out; // generated module
pub struct TestServer {
user: String
}
impl out::ApiInterface for TestServer {
async fn device_get(&self, _req: out::DeviceGetRequest) -> out::DeviceGetResponse {
out::DeviceGetResponse::Status200(out::Device{
key: "0107411222".into(),
key_user: self.user.clone(),
name: "device-kix".into(),
latitude: 34.43417,
longitude: 135.23278,
})
}
}
#[tokio::main]
async fn main() {
let port: u16 = std::env::var("PORT")
.unwrap_or("8080".to_string())
.parse()
.expect("PORT should be integer");
out::print_axum_router(port);
let api = TestServer {
user: "lzpel".into()
};
let app = out::axum_router(api).layer(axum::extract::DefaultBodyLimit::disable());
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{port}"))
.await
.unwrap();
axum::serve(listener, app)
.with_graceful_shutdown(async { tokio::signal::ctrl_c().await.unwrap() })
.await
.unwrap();
}You can run your server with generated out.rs and your main.rs like above code. You can check your api serves as you impleted.
$ cargo run
http://localhost:8080/api/ui$ curl http://localhost:8080/api/device/anyid
{
"key": "0107411222",
"key_user": "lzpel",
"name": "device-kix",
"latitude": 34.43417,
"longitude": 135.23278
}Open the output uri http://localhost:8080/api/ui show following swagger-ui page.
Render axum server source code using your custom jinja2 template.
use mandolin;
use serde_yaml;
use std::fs;
fn main() {
// read openapi.yaml
let input_api = serde_yaml::from_str(
fs::read_to_string("./openapi/openapi.yaml")
.unwrap()
.as_str(),
)
.unwrap();
let mut env = mandolin::environment(input_api).unwrap();
// add your templates
let content = fs::read_to_string("./templates/rust_axum.template").unwrap();
env.add_template("RUST_AXUM", &content).unwrap();
let content = fs::read_to_string("./templates/rust_schema.template").unwrap();
env.add_template("RUST_SCHEMA", &content).unwrap();
let content = fs::read_to_string("./templates/rust_operation.template").unwrap();
env.add_template("RUST_OPERATION", &content).unwrap();
// write the rendered output
let output = env.get_template("RUST_AXUM").unwrap().render(0).unwrap();
fs::write("examples/example_axum_generated_custom.rs", output).unwrap();
}- 0.2.3 add binary target
- 0.2.2 Fix bugs about no content response
- 0.2.1 Add impl AsRef<axum::http::Requestaxum::body::Body> for Requests
- 0.2.0
- update README.md
- fix many bugs.
- support parse multipart/form-data
- support catch-all path arguments like /files/{file_path} in axum
- 0.1.13
- support date schema {type: "string", format: "date-time" or "date"}
- add &self argument in rust interface()
- 0.1.12 add target "TYPESCRIPT_HONO" https://github.com/honojs/hono
- 0.1.11 update to flatten nested schema. prepare cli-command
mandolin-cli. - 0.1.7 hotfix
- 0.1.6 independent from regex, tera
- 0.1.5 fix ref filter
- 0.1.4 replace minijinja from tera
- 0.1.3
- simplify mandolin::Mandolin::new
pub fn new(api: OpenAPI) -> Result<Self, serde_yaml::Error>intopub fn new(api: OpenAPI) -> Self - remove mandolin::Mandolin::template_from_path
- move serde_yaml(deprecated) in dependency into in dev-dependency
- update README.md
- add examples
- rename mandolin::builtin into mandolin::templates
- exclude frontend from crate
- simplify mandolin::Mandolin::new
- 0.1.0 publish
- 月に舞う/武藤理恵 https://youtu.be/OVKkRj0di2I
- Suite Spagnola/C.Mandonico https://youtu.be/fCkcP_cuneU