Vespera maps your src/routes/ folder structure directly to URL paths. The vespera!() macro scans the folder at compile time — no manual Router::new().route(...) calls needed.
src/routes/
├── mod.rs → /
├── users.rs → /users
├── posts.rs → /posts
└── admin/
├── mod.rs → /admin
└── stats.rs → /admin/stats
The final URL for a handler is: file path prefix + #[route] path attribute.
// In src/routes/users.rs
#[vespera::route(get, path = "/{id}")]
pub async fn get_user(...) // → GET /users/{id}Handlers must be pub async fn. Private or non-async functions are silently ignored by the scanner.
// Ignored — private
async fn get_users() -> Json<Vec<User>> { ... }
// Ignored — not async
pub fn get_users() -> Json<Vec<User>> { ... }
// Discovered
pub async fn get_users() -> Json<Vec<User>> { ... }// GET /users (default method is GET)
#[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> { ... }
// PUT /users/{id} with tags and description
#[vespera::route(put, path = "/{id}", tags = ["users"], description = "Update user")]
pub async fn update_user(...) -> ... { ... }| Parameter | Type | Description |
|---|---|---|
| method | get, post, put, patch, delete, head, options | HTTP method (default: get) |
path | string | Path suffix appended to the file-based prefix |
tags | string array | OpenAPI tags for grouping in Swagger UI |
description | string | OpenAPI operation description |
The default folder is src/routes/. Change it with the dir parameter or the VESPERA_DIR environment variable:
// Scans src/api/ instead of src/routes/
let app = vespera!(dir = "api");Return Result<T, E> from handlers. Both T and E are included in the OpenAPI response schemas:
#[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() }))
}