Co-Pilot
Updated 24 days ago

vespera

Ddev-five-git
0.0k
dev-five-git/vespera
84
Agent Score

πŸ’‘ Summary

Vespera simplifies API development in Rust with FastAPI-like features and automatic OpenAPI generation.

🎯 Target Audience

Rust developers looking for API solutionsBackend engineers wanting to streamline API creationDevelopers transitioning from FastAPI to RustTeams adopting Rust for microservicesOpenAPI enthusiasts seeking Rust integrations

πŸ€– AI Roast: β€œPowerful, but the setup might scare off the impatient.”

Security AnalysisLow Risk

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

  1. Check #[derive(Schema)] on the type
  2. Check type is used in a route handler's input/output
  3. 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 From impl 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
5-Dim Analysis
Clarity8/10
Novelty8/10
Utility9/10
Completeness8/10
Maintainability9/10
Pros & Cons

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
toolCode Lib
92/ 100

β€œ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
toolCode Lib
90/ 100

β€œ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
toolCo-Pilot
90/ 100

β€œ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.