mandolin

Input openapi.json/yaml, output one server code in rust or typescript.
Online demo with wasm: https://lzpel.github.io/mandolin/
What is this.
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
Getting started
Render axum server code using builtin "RUST_AXUM" template
use mandolin;
use serde_yaml;
fn main() {
let input_openapi_path = std::env::args()
.nth(1)
.unwrap_or_else(|| "./openapi/openapi_plant.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();
let env = mandolin::environment(input_api).unwrap();
let output = env.get_template("RUST_AXUM").unwrap().render(0).unwrap();
std::fs::write("examples/readme_axum_generate_out.rs", output).unwrap();
}
use std::collections::HashMap;
use serde;
use std::future::Future;
pub trait ApiInterface{
fn authapi_email(&self, _req: AuthapiEmailRequest) -> impl Future<Output = AuthapiEmailResponse> + Send{async{Default::default()}}
fn authapi_callback_oauth(&self, _req: AuthapiCallbackOauthRequest) -> impl Future<Output = AuthapiCallbackOauthResponse> + Send{async{Default::default()}}
fn authapi_google(&self, _req: AuthapiGoogleRequest) -> impl Future<Output = AuthapiGoogleResponse> + Send{async{Default::default()}}
──────────────────────────────────────── 412 lines omitted ────────────────────────────────────────
}));
return router;
}
pub fn axum_router<S: ApiInterface + Sync + Send + 'static>(instance: S)->axum::Router{
let instance_arc=std::sync::Arc::new(instance);
axum::Router::new()
.nest_service("/api", axum_router_operations(instance_arc.clone()))
}
pub fn print_axum_router(port:u16){
println!("http://localhost:{}/api/ui", port);
}
pub struct TestServer{}
impl ApiInterface for TestServer{}
#[allow(dead_code)]
#[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; 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() {
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();
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();
let output = env.get_template("RUST_AXUM").unwrap().render(0).unwrap();
fs::write("examples/example_axum_generated_custom.rs", output).unwrap();
}
version
- 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> into pub 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
- 0.1.0 publish
my favorite mandolin music