# Dynami
A Rust library for automatic Axum router generation from directory structure. Generate type-safe routers for Axum 0.8+ by organizing your route handlers into a folder hierarchy.
## Features
- **File-system based routing** - Routes are automatically generated from your folder structure
- **Soft overwriting** - Preserves your custom code outside of generated sections
- **Smart route detection** - Won't overwrite routes you've manually defined
- **Dynamic routes** - Use `d_` prefix for path parameters (e.g., `d_id` -> `/{id}`)
- **Wildcard routes** - Support for multi-segment captures (e.g., `d_*rest` -> `/{*rest}`)
- **State detection** - Automatically detects and applies your AppState type
- **All HTTP methods** - GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD
- **Auto-generated handlers** - Creates default handlers for empty method files
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
dynami = "0.1"
[build-dependencies]
dynami = "0.1"
```
## Usage
### 1. Create a folder structure for your routes
```
src/routes/
├── mod.rs # Root router (optional AppState here)
├── get.rs # GET /
├── post.rs # POST /
└── api/
├── mod.rs # API router
├── get.rs # GET /api
└── d_id/
├── mod.rs # Dynamic router
└── get.rs # GET /api/{id}
```
### 2. Create a build.rs file
```rust
use dynami::format_routes;
fn main() {
format_routes("./src/routes").unwrap();
println!("cargo:rerun-if-changed=src/routes");
}
```
### 3. Run your build
```bash
cargo build
```
The library will generate:
- `router()` functions in each `mod.rs`
- `pub mod` declarations for subdirectories
- Default handlers for empty method files
- Proper type annotations with your AppState
### 4. Use the generated router in your application
```rust
mod routes;
#[tokio::main]
async fn main() {
let app = routes::router();
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.unwrap();
axum::serve(listener, app).await.unwrap();
}
```
## How it Works
### Generated Code with `dynami::generate!`
The library uses the `dynami::generate!` macro to mark generated code blocks:
```rust
use axum::Router;
use axum::routing::get;
pub fn router() -> Router {
let mut router = Router::new();
// You can add custom routes here - they won't be overwritten
router = router.route("/custom", get(custom_handler));
dynami::generate! {
// This block is auto-generated - don't edit manually
router = router.route("/", get(get::handler));
router = router.nest("/api", api::router());
}
router
}
```
**Key benefits:**
- Add custom routes before or after the generated block
- The generator detects your manual routes and won't duplicate them
- Smart import generation: only imports the HTTP methods actually used (e.g., `use axum::routing::{get, post}` only if both are used)
- If you remove `dynami::generate!`, the file won't be updated anymore
- The macro is a simple pass-through that returns its input, so the code compiles normally
### HTTP Method Files
Create files named after HTTP methods:
- `get.rs` -> GET handler
- `post.rs` -> POST handler
- `put.rs` -> PUT handler
- `delete.rs` -> DELETE handler
- `patch.rs` -> PATCH handler
- `options.rs` -> OPTIONS handler
- `head.rs` -> HEAD handler
### Dynamic Routes
Use the `d_` prefix for path parameters:
- `d_id/` -> `/{id}` route
- `d_user_id/` -> `/{user_id}` route
- `d_*rest/` -> `/{*rest}` wildcard route (captures remaining path)
### AppState Detection
Add your state struct to the root `mod.rs`:
```rust
#[derive(Clone)]
pub struct AppState {
pub db: Database,
}
pub fn router() -> Router<AppState> {
let mut router = Router::new();
dynami::generate! {
router = router.route("/", get(get::handler));
}
router
}
```
The library will:
1. Detect the `AppState` struct (or any struct with "State" in its name)
2. Generate `Router<AppState>` for all routers
3. Include `State` extractors in default handlers
## Examples
### Simple API
```
routes/
├── mod.rs
├── get.rs # GET / - list all
└── post.rs # POST / - create new
```
Generates:
```rust
use axum::Router;
use axum::routing::{get, post};
pub fn router() -> Router {
let mut router = Router::new();
dynami::generate! {
router = router.route("/", get(get::handler).post(post::handler));
}
router
}
```
### Nested Routes
```
routes/
├── mod.rs
├── get.rs
└── api/
├── mod.rs
├── get.rs
└── users/
├── mod.rs
└── get.rs
```
Generates in `routes/mod.rs`:
```rust
pub mod api;
use axum::Router;
use axum::routing::get;
pub fn router() -> Router {
let mut router = Router::new();
dynami::generate! {
router = router.route("/", get(get::handler));
router = router.nest("/api", api::router());
}
router
}
```
### Mixing Manual and Generated Routes
```rust
// routes/mod.rs
use axum::Router;
use axum::routing::get;
pub mod api;
// Custom handler defined locally
async fn health_check() -> &'static str {
"OK"
}
pub fn router() -> Router {
let mut router = Router::new();
// Manual route - generator will detect this and skip generating GET /
router = router.route("/health", get(health_check));
dynami::generate! {
// Only routes not already defined above will be generated
router = router.nest("/api", api::router());
}
router
}
```
### With AppState
```rust
// routes/mod.rs
use sqlx::PgPool;
#[derive(Clone)]
pub struct AppState {
pub pool: PgPool,
}
pub fn router() -> Router<AppState> {
let mut router = Router::new();
dynami::generate! {
router = router.route("/", get(get::handler));
}
router
}
```
```rust
// routes/get.rs
use axum::extract::State;
use axum::response::IntoResponse;
use super::AppState;
pub async fn handler(State(state): State<AppState>) -> impl IntoResponse {
// Use state.pool here
"Hello World"
}
```
## Default Handlers
Empty method files get default handlers:
```rust
// Without state
use axum::response::IntoResponse;
pub async fn handler() -> impl IntoResponse {
"OK"
}
```
```rust
// With state (when AppState is detected)
use axum::extract::State;
use axum::response::IntoResponse;
pub async fn handler(State(state): State<AppState>) -> impl IntoResponse {
"OK"
}
```
## Best Practices
1. **Run in build.rs** - Generate routes during build time
2. **Keep the `dynami::generate!` block** - If you remove it, the file won't be updated
3. **Add custom code outside the generate block** - Your code won't be overwritten
4. **Use meaningful folder names** - They become route paths
5. **Implement handlers** - Replace default "OK" responses with real logic
6. **Define custom routes before the generate block** - They'll be detected and not duplicated
## Limitations
- Axum doesn't support regex in path parameters (use middleware for validation)
- Route generation happens at build time (requires rebuild for new routes)
- Wildcard routes must use `d_*` prefix (e.g., `d_*rest`)
## License
This project is open source and available under the MIT License.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.