# mandolin

[![GitHub License](https://img.shields.io/github/license/lzpel/cloudmama)](https://github.com/lzpel/mandolin/blob/main/LICENSE)
[![Crates.io](https://img.shields.io/crates/v/mandolin.svg?logo=rust)](https://crates.io/crates/mandolin)

Mandolin is a tool to generate server-side code from OpenAPI specifications (YAML/JSON).
It currently supports:

- **Rust**: [axum](https://github.com/tokio-rs/axum)
- **TypeScript**: [Hono](https://github.com/honojs/hono)

Mandolin adopts a "Logic in Templates" design philosophy, where Rust handles data preparation and `$ref` resolution, while templates handle the code assembly.

## Getting started

Install mandolin from source:

```bash
$ cargo install --path .
```

Render axum server code using builtin "RUST_AXUM" template:

```bash
$ mandolin -i ./openapi/openapi_plant.yaml -t RUST_AXUM -o ./examples/example_server.rs
```

Using pipes:

```bash
$ cat openapi.json | mandolin -i - -t TYPESCRIPT_HONO > ./examples/server.ts
```

## You can also use mandolin as library

Mandolin exposes a simple API `mandolin::environment` that returns a configured Minijinja environment.

```rust 
use mandolin;
use openapiv3::OpenAPI;

fn main() {
    // 1. Read OpenAPI file
    let input = std::fs::read_to_string("./openapi/openapi_plant.yaml").unwrap();
    let api: OpenAPI = serde_yaml::from_str(&input).unwrap();

    // 2. Create environment (References are auto-resolved here)
    let env = mandolin::environment(api).unwrap();

    // 3. Render
    let output = env.get_template("RUST_AXUM").unwrap().render(0).unwrap();
    
    std::fs::write("examples/generated_server.rs", output).unwrap();
}
```

## Example of generated code

The generated code defines a trait `ApiInterface`. You only need to implement this trait.

```rust
// This is generated by mandolin

use axum;
use serde;
use std::future::Future;

pub trait ApiInterface {
    // get /device/{key}
    fn device_get(&self, key: String) -> impl Future<Output = DeviceGetResponse> + Send {
        async { DeviceGetResponse::Status404 }
    }
    
    // post /device
    fn device_create(&self, req: DeviceCreateRequest) -> impl Future<Output = DeviceCreateResponse> + Send {
        async { DeviceCreateResponse::Status201(Default::default()) }
    }
}
// ... Request/Response structs and Router definition follows ...
```

## Running the generated server with your implementation

You can import the generated module and implement the trait to build your server.

```rust
mod generated; // The file generated by mandolin

use generated::*;
use axum::{Router, serve};
use tokio::net::TcpListener;

struct MyServer {
    db_url: String,
}

// Implement the business logic
impl ApiInterface for MyServer {
    async fn device_get(&self, key: String) -> DeviceGetResponse {
        // Your implementation here...
        DeviceGetResponse::Status200(Device {
            key,
            name: "example-device".to_string(),
            ..Default::default()
        })
    }
}

#[tokio::main]
async fn main() {
    let api = MyServer { db_url: "postgres://...".to_string() };
    
    // 'axum_router' is generated by mandolin
    let app = axum_router(api); 
    
    let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();
    serve(listener, app).await.unwrap();
}
```

## Custom Templates

You can easily use your own templates.
Dependencies are minimized, and helpers like `include_ref` are no longer needed because `$ref` is pre-resolved.

```rust
use mandolin;
use openapiv3::OpenAPI;
use std::fs;

fn main() {
    let input = fs::read_to_string("./openapi/openapi.yaml").unwrap();
    let api: OpenAPI = serde_yaml::from_str(&input).unwrap();
    
    let mut env = mandolin::environment(api).unwrap();
    
    // Add your custom template
    // Note: Templates are now single-file and self-contained
    let content = fs::read_to_string("./my_templates/custom_rust.template").unwrap();
    env.add_template("CUSTOM_RUST", &content).unwrap();

    let output = env.get_template("CUSTOM_RUST").unwrap().render(0).unwrap();
    fs::write("server.rs", output).unwrap();
}
```

## Version History

- **0.4.0-alpha.1**
  - **Major Re-architecture**: "Logic in Templates".
  - Moved logic from Rust to templates.
  - `$ref` is now pre-resolved in Rust.
  - Templates are consolidated into single files (no more `include` hell).
  - TypeScript (Hono) support improved.

- 0.2.5
  - Improve `rust_axum.template` to correctly set `Content-Type` header
- 0.2.4
  - Internal bug fixes and improvements to response handling
- 0.2.3 add binary target
- 0.2.2 Fix bugs about no content response
- 0.2.1 Add impl AsRef<axum::http::Request<axum::body::Body>> for Requests
- 0.2.0
  - support parse multipart/form-data
  - support catch-all path arguments
- 0.1.13 support date schema
- 0.1.12 add target "TYPESCRIPT_HONO"
- 0.1.0 publish

## My favorite mandolin music

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