mandolin 0.1.13

Input openapi.json/yaml, output server source code in rust.
Documentation
# mandolin

<a href="https://crates.io/crates/mandolin"><img alt="crates.io" src="https://img.shields.io/crates/v/mandolin.svg?style=for-the-badge&logo=rust" height="20"/></a>

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 server code from openapi specification and jinja2 templates.

Currently, mandolin provide 2 builtin templates for following frameworks

- axum(Rust) https://github.com/tokio-rs/axum
- hono(Typescript) https://github.com/honojs/hono

## Getting started

Render axum server code using builtin "RUST_AXUM" template

```rust:examples/example_builtin_axum.rs
use mandolin;
use serde_yaml;
use std::fs;
fn main() {
	// read openapi.yaml
	let input_string=fs::read_to_string("./openapi/openapi_petstore.yaml").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();
	fs::write("./out/server_builtin_axum.rs", output).unwrap();
}
```

output typescript code `./out/server_builtin_axum.rs`

```rust
#![allow(non_camel_case_types)]
#![allow(unused_variables)]

// This is generated by mandolin https://github.com/lzpel/mandolin from OpenApi specification

/* Cargo.toml for build this server

[dependencies]
serde= "*"
serde_json= "*"
axum="*"
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{
	// put /pet
	fn updatepet(request: UpdatepetRequest) -> impl Future<Output = UpdatepetResponse> + Send{async{Default::default()}}
	// post /pet
	fn addpet(request: AddpetRequest) -> impl Future<Output = AddpetResponse> + Send{async{Default::default()}}
	// get /pet/findByStatus
	fn findpetsbystatus(request: FindpetsbystatusRequest) -> impl Future<Output = FindpetsbystatusResponse> + Send{async{Default::default()}}
	// get /pet/findByTags
...
use axum;
pub fn axum_router_operations<S: ApiInterface + Sync + Send + 'static>(instance :std::sync::Arc<S>)->axum::Router{
	let router = axum::Router::new();
	let i = instance.clone();
	let router = router.route("/pet", axum::routing::put(|
			path: axum::extract::Path<HashMap<String,String>>,
			query: axum::extract::Query<HashMap<String,String>>,
			header: axum::http::HeaderMap,
			body: axum::body::Bytes,
		| async move{
			let ret=S::updatepet(i.as_ref(), UpdatepetRequest{
				body:match serde_json::from_slice(body.to_vec().as_slice()){Ok(v)=>v,Err(v) => { return (axum::http::StatusCode::INTERNAL_SERVER_ERROR,[(axum::http::header::CONTENT_TYPE, "text/plain")], format!("{:?}", v).as_bytes().to_vec())}},
			}).await;
			match ret{
				UpdatepetResponse::Status200(v)=> (axum::http::StatusCode::from_u16(200).unwrap(),[(axum::http::header::CONTENT_TYPE, "application/json")],serde_json::to_vec_pretty(&v).expect("error serialize response json")),
				UpdatepetResponse::Status400=> (axum::http::StatusCode::from_u16(400).unwrap(),[(axum::http::header::CONTENT_TYPE, "text/plain")], "Invalid ID supplied".as_bytes().to_vec()),
				UpdatepetResponse::Status404=> (axum::http::StatusCode::from_u16(404).unwrap(),[(axum::http::header::CONTENT_TYPE, "text/plain")], "Pet not found".as_bytes().to_vec()),
				UpdatepetResponse::Status422=> (axum::http::StatusCode::from_u16(422).unwrap(),[(axum::http::header::CONTENT_TYPE, "text/plain")], "Validation exception".as_bytes().to_vec()),
			}
		}));
	let i = instance.clone();
	let router = router.route("/pet", axum::routing::post(|
			path: axum::extract::Path<HashMap<String,String>>,
...
pub struct TestServer{}
impl SwaggerPetstoreOpenapi30 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 app = axum_router::<TestServer>().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`.

```rust
use generated::ApiInterface;
pub struct YourServer{}
impl ApiInterface for YourServer{
	// get /hello
	async fn paths_hello_get(&self, _req: PathsHelloGetRequest) -> PathsHelloGetResponse{
		PathsHelloGetResponse::Status200(PathsHelloGetResponses200ContentApplicationJsonSchema{
			message: Some("Hello Mandolin".to_string()),
		})
	}
}
#[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 = YourServer{};
	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();
}
```

---

Render honojs server code using builtin "TYPESCRIPT_HONO" template

```rust:examples/example_builtin_hono.rs
use mandolin;
use serde_yaml;
use std::fs;
fn main() {
	// read openapi.yaml
	let input_string=fs::read_to_string("./openapi/openapi_petstore.yaml").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("TYPESCRIPT_HONO").unwrap().render(0).unwrap();
	fs::write("./out/server_builtin_hono.ts", output).unwrap();
}
```

output typescript code `out/server_builtin_hono.ts`

```typescript
import { Hono } from 'hono'
import { serve } from '@hono/node-server'



// This is generated by mandolin https://github.com/lzpel/mandolin from OpenApi specification
// Request of updatepet
type UpdatepetRequest = {
	body:Pet,
}
// Response of updatepet
type UpdatepetResponse =
	| { code: 200; body:Pet}
	| { code: 400;}
	| { code: 404;}
	| { code: 422;}
// Request of addpet
type AddpetRequest = {
	body:Pet,
}
...
	app.delete('/user/:username', async (c) => {
		if (implement.deleteuser===undefined)return c.text("not yet implemented", 500)
		const request: Partial<DeleteuserRequest> = {}
		{
			let username = c.req.param("username")
			if(username===undefined)return c.text("required parameter 'username' is not in 'path'", 400)
			request.username = username;
		}
		const response = await implement.deleteuser(request as DeleteuserRequest)
		switch (response.code){
			case 400:
				return c.text("Invalid username supplied")
			case 404:
				return c.text("User not found")
		}
	})
}
class TestServer implements SwaggerPetstoreOpenapi30{}
export function main(){
	const app = new Hono()
	addHonoOperations(app,new TestServer());
	serve({
		fetch: app.fetch,
		port: 3000
	}, (info) => {
		console.log(`Server is running on http://localhost:${info.port}`)
	})
}
```

---

Render axum server source code using your custom jinja2 template.

```rust:examples/example_custom.rs
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.as_str()).unwrap();

	let content = fs::read_to_string("./templates/rust_schema.template").unwrap();
	env.add_template("RUST_SCHEMA", content.as_str()).unwrap();

	let content = fs::read_to_string("./templates/rust_operation.template").unwrap();
	env.add_template("RUST_OPERATION", content.as_str())
		.unwrap();

	// write the rendered output
	let output = env.get_template("RUST_AXUM").unwrap().render(0).unwrap();
	fs::write("./out/server_builtin.rs", output).unwrap();
}
```

## version

- 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

- 月に舞う/武藤理恵 https://youtu.be/OVKkRj0di2I
- Suite Spagnola/C.Mandonico https://youtu.be/fCkcP_cuneU