Vespera generates a complete OpenAPI 3.1 spec from your Rust types at compile time. Derive Schema on any type used in a handler's input or output and it appears in the spec automatically.
#[derive(Serialize, Deserialize, vespera::Schema)]
pub struct User {
pub id: u32,
pub name: String,
pub email: String,
pub bio: Option<String>, // optional — not in `required` array
}Vespera respects all standard serde attributes:
#[derive(Serialize, Deserialize, vespera::Schema)]
#[serde(rename_all = "camelCase")]
pub struct CreateUserRequest {
pub user_name: String, // → "userName" in OpenAPI
pub email: String,
#[serde(rename = "fullName")]
pub name: String, // → "fullName" in OpenAPI
#[serde(skip)]
pub internal_id: u64, // excluded from schema
pub bio: Option<String>, // optional field
}| Rust Type | OpenAPI Schema |
|---|---|
String, &str | string |
i8–i128, u8–u128 | integer |
f32, f64 | number |
bool | boolean |
Vec<T> | array with items |
Option<T> | T (parent marks field as optional) |
HashMap<K, V> | object with additionalProperties |
BTreeSet<T>, HashSet<T> | array with uniqueItems: true |
Uuid | string with format: uuid |
Decimal | string with format: decimal |
NaiveDate | string with format: date |
NaiveTime | string with format: time |
DateTime, DateTimeWithTimeZone | string with format: date-time |
FieldData<NamedTempFile> | string with format: binary |
() | empty response (204 No Content) |
| Custom struct | $ref to components/schemas |
All type parameters must also derive Schema:
#[derive(Schema)]
struct Paginated<T: Schema> {
items: Vec<T>,
total: u32,
page: u32,
}schema_type! has first-class support for SeaORM models. Relation fields are converted automatically:
#[derive(Clone, Debug, DeriveEntityModel)]
#[sea_orm(table_name = "memos")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub title: String,
pub user_id: i32,
pub user: BelongsTo<super::user::Entity>, // → Option<Box<UserSchema>>
pub comments: HasMany<super::comment::Entity>, // → Vec<CommentSchema>
pub created_at: DateTimeWithTimeZone, // → chrono::DateTime<FixedOffset>
}
vespera::schema_type!(Schema from Model, name = "MemoSchema");| SeaORM Type | Generated Schema Type |
|---|---|
HasOne<Entity> | Box<Schema> or Option<Box<Schema>> |
BelongsTo<Entity> | Option<Box<Schema>> |
HasMany<Entity> | Vec<Schema> |
DateTimeWithTimeZone | chrono::DateTime<FixedOffset> |
Circular references (e.g. User ↔ Memo) are detected automatically and handled by inlining fields to prevent infinite recursion.
Fields with SeaORM database defaults get default values in the generated schema:
| SeaORM Attribute | OpenAPI Default |
|---|---|
primary_key (Uuid) | "00000000-0000-0000-0000-000000000000" |
primary_key (i32/i64) | 0 |
default_value = "NOW()" | "1970-01-01T00:00:00+00:00" |
default_value = "gen_random_uuid()" | "00000000-0000-0000-0000-000000000000" |
default_value = "true" | true |
requiredis determined solely by nullability (Option<T>). Fields with defaults are stillrequiredunless they areOption<T>.
Pass parameters to vespera!() to control the spec:
let app = vespera!(
openapi = "openapi.json", // write spec to this file at compile time
title = "My API",
version = "1.0.0",
docs_url = "/docs", // Swagger UI
redoc_url = "/redoc", // ReDoc
servers = [
{ url = "https://api.example.com", description = "Production" },
{ url = "http://localhost:3000", description = "Development" }
]
);See vespera! Macro for the full parameter reference.