π‘ Summary
Vespera simplifies API development in Rust with FastAPI-like features and automatic OpenAPI generation.
π― Target Audience
π€ AI Roast: βPowerful, but the setup might scare off the impatient.β
Risk: Low. Review: shell/CLI command execution; outbound network access (SSRF, data egress); filesystem read/write scope and path traversal. Run with least privilege and audit before enabling in production.
name: vespera description: Build APIs with Vespera - FastAPI-like DX for Rust/Axum. Covers route handlers, Schema derivation, and OpenAPI generation.
Vespera Usage Guide
Vespera = FastAPI DX for Rust. Zero-config OpenAPI 3.1 generation via compile-time macro scanning.
Quick Start
// 1. Main entry - vespera! macro handles everything let app = vespera!( openapi = "openapi.json", // writes file at compile time title = "My API", version = "1.0.0", docs_url = "/docs", // Swagger UI redoc_url = "/redoc" // ReDoc alternative ); // 2. Route handlers - MUST be pub async fn #[vespera::route(get, path = "/{id}", tags = ["users"])] pub async fn get_user(Path(id): Path<u32>) -> Json<User> { ... } // 3. Custom types - derive Schema for OpenAPI inclusion #[derive(Serialize, Deserialize, vespera::Schema)] pub struct User { id: u32, name: String }
Type Mapping Reference
| Rust Type | OpenAPI Schema | Notes |
|-----------|----------------|-------|
| String, &str | string | |
| i8-i128, u8-u128 | integer | |
| f32, f64 | number | |
| bool | boolean | |
| Vec<T> | array + items | |
| Option<T> | T (nullable context) | Parent marks as optional |
| HashMap<K,V> | object + additionalProperties | |
| () | empty response | 204 No Content |
| Custom struct | $ref | Must derive Schema |
Extractor Mapping Reference
| Axum Extractor | OpenAPI Location | Notes |
|----------------|------------------|-------|
| Path<T> | path parameter | T can be tuple or struct |
| Query<T> | query parameters | Struct fields become params |
| Json<T> | requestBody | application/json |
| Form<T> | requestBody | application/x-www-form-urlencoded |
| State<T> | ignored | Internal, not API |
| Extension<T> | ignored | Internal, not API |
| TypedHeader<T> | header parameter | |
| HeaderMap | ignored | Too dynamic |
Route Handler Requirements
// β Private function - NOT discovered async fn get_users() -> Json<Vec<User>> { ... } // β Non-async function - NOT supported pub fn get_users() -> Json<Vec<User>> { ... } // β Must be pub async fn pub async fn get_users() -> Json<Vec<User>> { ... }
File Structure β URL Mapping
src/routes/
βββ mod.rs β / (root routes)
βββ users.rs β /users
βββ posts.rs β /posts
βββ admin/
βββ mod.rs β /admin
βββ stats.rs β /admin/stats
Handler path is: {file_path} + {#[route] path}
// In src/routes/users.rs #[vespera::route(get, path = "/{id}")] pub async fn get_user(...) // β GET /users/{id}
Serde Integration
Vespera respects serde attributes:
#[derive(Serialize, Deserialize, Schema)] #[serde(rename_all = "camelCase")] // β Respected in schema pub struct UserResponse { user_id: u32, // β "userId" in JSON Schema #[serde(rename = "fullName")] // β Respected name: String, // β "fullName" in JSON Schema #[serde(default)] // β Marks as optional in schema bio: Option<String>, #[serde(skip)] // β Excluded from schema internal_id: u64, }
Debugging Tips
Schema Not Appearing
- Check
#[derive(Schema)]on the type - Check type is used in a route handler's input/output
- Check for generic types - all type params need Schema
// Generic types need Schema on all params #[derive(Schema)] struct Paginated<T: Schema> { // T must also derive Schema items: Vec<T>, total: u32, }
Macro Expansion
# See what vespera! generates cargo expand # Validate OpenAPI output npx @apidevtools/swagger-cli validate openapi.json
Environment Variables
| Variable | Purpose | Default |
|----------|---------|---------|
| VESPERA_DIR | Route folder name | routes |
| VESPERA_OPENAPI | OpenAPI output path | none |
| VESPERA_TITLE | API title | API |
| VESPERA_VERSION | API version | CARGO_PKG_VERSION |
| VESPERA_DOCS_URL | Swagger UI path | none |
| VESPERA_REDOC_URL | ReDoc path | none |
| VESPERA_SERVER_URL | Server URL | http://localhost:3000 |
schema_type! Macro (RECOMMENDED)
ALWAYS prefer
schema_type!over manually defining request/response structs.Benefits:
- Single source of truth (your model)
- Auto-generated
Fromimpl for easy conversion- Automatic type resolution (enums, custom types β absolute paths)
- SeaORM relation support (HasOne, BelongsTo, HasMany)
- No manual field synchronization
Why Not Manual Structs?
// β BAD: Manual struct definition - requires sync with Model #[derive(Serialize, Deserialize, Schema)] pub struct UserResponse { pub id: i32, pub name: String, pub email: String, // Forgot to add new field? Schema out of sync! } // β GOOD: Derive from Model - always in sync schema_type!(UserResponse from crate::models::user::Model, omit = ["password_hash"]);
Basic Syntax
// Pick specific fields schema_type!(CreateUserRequest from crate::models::user::Model, pick = ["name", "email"]); // Omit specific fields schema_type!(UserResponse from crate::models::user::Model, omit = ["password_hash", "internal_id"]); // Add new fields (NOTE: no From impl generated when using add) schema_type!(UpdateUserRequest from crate::models::user::Model, pick = ["name"], add = [("id": i32)]); // Rename fields schema_type!(UserDTO from crate::models::user::Model, rename = [("id", "user_id")]); // Partial updates (all fields become Option<T>) schema_type!(UserPatch from crate::models::user::Model, partial); // Partial updates (specific fields only) schema_type!(UserPatch from crate::models::user::Model, partial = ["name", "email"]); // Custom serde rename strategy schema_type!(UserSnakeCase from crate::models::user::Model, rename_all = "snake_case"); // Custom OpenAPI schema name schema_type!(Schema from Model, name = "UserSchema"); // Skip Schema derive (won't appear in OpenAPI) schema_type!(InternalDTO from Model, ignore); // Disable Clone derive schema_type!(LargeResponse from SomeType, clone = false);
Same-File Model Reference
When the model is in the same file, use simple name with name parameter:
// In src/models/user.rs pub struct Model { pub id: i32, pub name: String, pub status: UserStatus, // Custom enum - auto-resolved to absolute path } pub enum UserStatus { Active, Inactive } // Simple `Model` path works - module path inferred from file location vespera::schema_type!(Schema from Model, name = "UserSchema");
Cross-File References
Reference structs from other files using full module paths:
// In src/routes/users.rs use vespera::schema_type; // Reference model from src/models/user.rs schema_type!(CreateUserRequest from crate::models::user::Model, pick = ["name", "email"]);
The macro reads the source file at compile time - no special annotations needed on the source struct.
Auto-Generated From Impl
When add is NOT used, schema_type! generates a From impl for easy conversion:
// This: schema_type!(UserResponse from crate::models::user::Model, omit = ["password_hash"]); // Generates: pub struct UserResponse { id, name, email, created_at } impl From<crate::models::user::Model> for UserResponse { fn from(source: crate::models::user::Model) -> Self { Self { id: source.id, name: source.name, ... } } } // Usage: let model: Model = db.find_user(id).await?; Json(model.into()) // Easy conversion!
Note: From is NOT generated when add is used (can't auto-populate added fields).
Parameters
| Parameter | Description | Example |
|-----------|-------------|---------|
| pick | Include only these fields | pick = ["name", "email"] |
| omit | Exclude these fields | omit = ["password"] |
| rename | Rename fields | rename = [("id", "user_id")] |
| add | Add new fields (disables From impl) | add = [("extra": String)] |
| partial | Make fields optional for PATCH | partial or partial = ["name"] |
| name | Custom OpenAPI schema name | name = "UserSchema" |
| rename_all | Serde rename strategy | rename_all = "camelCase" |
| ignore | Skip Schema derive | bare keyword |
| clone | Control Clone derive (default: true) | clone = false |
SeaORM Integration (RECOMMENDED)
schema_type! has first-class SeaORM support with automatic relation handling:
// src/models/memo.rs #[derive(Clone, Debug, DeriveEntityModel)] #[sea_orm(table_name = "memo")] pub struct Model { #[sea_orm(primary_key)] pub id: i32, pub title: String, pub user_id: i32, pub status: MemoStatus, // Custom enum pub user: BelongsTo<super::user::Entity>, // β Option<Box<UserSchema>> pub comments: HasMany<super::comment::Entity>, // β Vec<CommentSchema> pub created_at: DateTimeWithTimeZone, // β chrono::DateTime<FixedOffset> } #[derive(EnumIter, DeriveActiveEnum, Serialize, Deserialize, Schema)] pub enum MemoStatus { Draft, Published, Archived } // Generates Schema with proper types - no imports needed! vespera::schema_type!(Schema from Model, name = "MemoSchema");
Automatic Type Conversions:
| SeaORM Type | Generated Type | Notes |
|-------------|---------------|-------|
| HasOne<Entity> | Box<Schema> or Option<Box<Schema>> | Based on FK nullability |
| BelongsTo<Entity> | Option<Box<Schema>> | Always optional |
| HasMany<Entity> | Vec<Schema> | |
| DateTimeWithTimeZone | vespera::chrono::DateTime<FixedOffset> | No SeaORM import needed |
| Custom enums | crate::module::EnumName | Auto-resolved to absolute path |
Circular Reference Handling: Automatically detected and handled by inlining fields.
Complete Example
// src/models/user.rs (Sea-ORM entity) #[derive(Clone, Debug, DeriveEntityModel, Serialize, Deserialize)] #[sea_orm(table_name = "us
Pros
- Zero-config OpenAPI generation
- Strong type safety with Rust
- Easy integration with existing Rust projects
- Supports advanced features like SeaORM
Cons
- Limited to Rust ecosystem
- Steeper learning curve for newcomers
- May require additional setup for complex APIs
Related Skills
pytorch
SβIt's the Swiss Army knife of deep learning, but good luck figuring out which of the 47 installation methods is the one that won't break your system.β
agno
SβIt promises to be the Kubernetes for agents, but let's see if developers have the patience to learn yet another orchestration layer.β
nuxt-skills
SβIt's essentially a well-organized cheat sheet that turns your AI assistant into a Nuxt framework parrot.β
Disclaimer: This content is sourced from GitHub open source projects for display and rating purposes only.
Copyright belongs to the original author dev-five-git.
