| Crates.io | vespera_macro |
| lib.rs | vespera_macro |
| version | 0.1.23 |
| created_at | 2025-11-26 03:06:43.680463+00 |
| updated_at | 2026-01-25 14:19:08.214188+00 |
| description | Procedural macros for Vespera - automatic route discovery and OpenAPI generation |
| homepage | |
| repository | https://github.com/dev-five-git/vespera |
| max_upload_size | |
| id | 1950879 |
| size | 477,090 |
FastAPI-like developer experience for Rust. Zero-config OpenAPI 3.1 generation for Axum.
// That's it. Swagger UI at /docs, OpenAPI at openapi.json
let app = vespera!(openapi = "openapi.json", docs_url = "/docs");
| Feature | Vespera | Manual Approach |
|---|---|---|
| Route registration | Automatic (file-based) | Manual Router::new().route(...) |
| OpenAPI spec | Generated at compile time | Hand-written or runtime generation |
| Schema extraction | From Rust types | Manual JSON Schema |
| Swagger UI | Built-in | Separate setup |
| Type safety | Compile-time verified | Runtime errors |
[dependencies]
vespera = "0.1"
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
src/
├── main.rs
└── routes/
└── users.rs
src/routes/users.rs:
use axum::{Json, Path};
use serde::{Deserialize, Serialize};
use vespera::Schema;
#[derive(Serialize, Deserialize, Schema)]
pub struct User {
pub id: u32,
pub name: String,
}
/// Get user by ID
#[vespera::route(get, path = "/{id}", tags = ["users"])]
pub async fn get_user(Path(id): Path<u32>) -> Json<User> {
Json(User { id, name: "Alice".into() })
}
/// Create a new user
#[vespera::route(post, tags = ["users"])]
pub async fn create_user(Json(user): Json<User>) -> Json<User> {
Json(user)
}
src/main.rs:
use vespera::vespera;
#[tokio::main]
async fn main() {
let app = vespera!(
openapi = "openapi.json",
title = "My API",
docs_url = "/docs"
);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
println!("Swagger UI: http://localhost:3000/docs");
axum::serve(listener, app).await.unwrap();
}
cargo run
# Open http://localhost:3000/docs
File structure maps to URL paths automatically:
src/routes/
├── mod.rs → /
├── users.rs → /users
├── posts.rs → /posts
└── admin/
├── mod.rs → /admin
└── stats.rs → /admin/stats
Handlers must be pub async fn with the #[vespera::route] attribute:
// GET /users (default method)
#[vespera::route]
pub async fn list_users() -> Json<Vec<User>> { ... }
// POST /users
#[vespera::route(post)]
pub async fn create_user(Json(user): Json<User>) -> Json<User> { ... }
// GET /users/{id}
#[vespera::route(get, path = "/{id}")]
pub async fn get_user(Path(id): Path<u32>) -> Json<User> { ... }
// Full options
#[vespera::route(put, path = "/{id}", tags = ["users"], description = "Update user")]
pub async fn update_user(...) -> ... { ... }
Derive Schema on types used in request/response bodies:
#[derive(Serialize, Deserialize, vespera::Schema)]
#[serde(rename_all = "camelCase")] // Serde attributes are respected
pub struct CreateUserRequest {
pub user_name: String, // → "userName" in OpenAPI
pub email: String,
#[serde(default)]
pub bio: Option<String>, // Optional field
}
| Extractor | OpenAPI Mapping |
|---|---|
Path<T> |
Path parameters |
Query<T> |
Query parameters |
Json<T> |
Request body (application/json) |
Form<T> |
Request body (form-urlencoded) |
TypedHeader<T> |
Header parameters |
State<T> |
Ignored (internal) |
#[derive(Serialize, Schema)]
pub struct ApiError {
pub message: String,
}
#[vespera::route(get, path = "/{id}")]
pub async fn get_user(Path(id): Path<u32>) -> Result<Json<User>, (StatusCode, Json<ApiError>)> {
if id == 0 {
return Err((StatusCode::NOT_FOUND, Json(ApiError { message: "Not found".into() })));
}
Ok(Json(User { id, name: "Alice".into() }))
}
vespera! Macro Referencelet app = vespera!(
dir = "routes", // Route folder (default: "routes")
openapi = "openapi.json", // Output path (writes file at compile time)
title = "My API", // OpenAPI info.title
version = "1.0.0", // OpenAPI info.version (default: CARGO_PKG_VERSION)
docs_url = "/docs", // Swagger UI endpoint
redoc_url = "/redoc", // ReDoc endpoint
servers = [ // OpenAPI servers
{ url = "https://api.example.com", description = "Production" },
{ url = "http://localhost:3000", description = "Development" }
],
merge = [crate1::App1, crate2::App2] // Merge child vespera apps
);
export_app! Macro ReferenceExport a vespera app for merging into other apps:
// Basic usage (scans "routes" folder by default)
vespera::export_app!(MyApp);
// Custom directory
vespera::export_app!(MyApp, dir = "api");
Generates a struct with:
MyApp::OPENAPI_SPEC: &'static str - The OpenAPI JSON specMyApp::router() -> Router - Function returning the Axum routerAll parameters support environment variable fallbacks:
| Parameter | Environment Variable |
|---|---|
dir |
VESPERA_DIR |
openapi |
VESPERA_OPENAPI |
title |
VESPERA_TITLE |
version |
VESPERA_VERSION |
docs_url |
VESPERA_DOCS_URL |
redoc_url |
VESPERA_REDOC_URL |
servers |
VESPERA_SERVER_URL + VESPERA_SERVER_DESCRIPTION |
Priority: Macro parameter > Environment variable > Default
let app = vespera!(docs_url = "/docs")
.with_state(AppState { db: pool });
let app = vespera!(docs_url = "/docs")
.layer(CorsLayer::permissive())
.layer(TraceLayer::new_for_http());
let app = vespera!(
openapi = ["openapi.json", "docs/api-spec.json"]
);
// Scans src/api/ instead of src/routes/
let app = vespera!("api");
// Or explicitly
let app = vespera!(dir = "api");
Combine routes and OpenAPI specs from multiple vespera apps at compile time:
Child app (e.g., third crate):
// src/lib.rs
mod routes;
// Export app for merging (dir defaults to "routes")
vespera::export_app!(ThirdApp);
// Or with custom directory
// vespera::export_app!(ThirdApp, dir = "api");
Parent app:
// src/main.rs
use vespera::vespera;
let app = vespera!(
openapi = "openapi.json",
docs_url = "/docs",
merge = [third::ThirdApp] // Merges router AND OpenAPI spec
)
.with_state(app_state);
This automatically:
| Rust Type | OpenAPI Schema |
|---|---|
String, &str |
string |
i32, u64, etc. |
integer |
f32, f64 |
number |
bool |
boolean |
Vec<T> |
array with items |
Option<T> |
nullable T |
HashMap<K, V> |
object with additionalProperties |
| Custom struct | $ref to components/schemas |
vespera/
├── crates/
│ ├── vespera/ # Main crate - re-exports everything
│ ├── vespera_core/ # OpenAPI types and abstractions
│ └── vespera_macro/ # Proc-macros (compile-time magic)
└── examples/
└── axum-example/ # Complete example application
git clone https://github.com/dev-five-git/vespera.git
cd vespera
# Build & test
cargo build
cargo test --workspace
# Run example
cd examples/axum-example
cargo run
# → http://localhost:3000/docs
See SKILL.md for development guidelines and architecture details.
Apache-2.0
Inspired by FastAPI's developer experience and Next.js's file-based routing.