Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changepacks/changepack_log_z-BQF0lAvHD8CkNVDl4dG.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"changes":{"crates/vespera/Cargo.toml":"Patch","crates/vespera_macro/Cargo.toml":"Patch","crates/vespera_core/Cargo.toml":"Patch"},"note":"Implement description, async error","date":"2025-12-31T07:14:07.530788400Z"}
7 changes: 7 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(cargo test:*)"
]
}
}
67 changes: 67 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,73 @@ pub async fn get_user(id: u32) -> Json<User> {
}
```

### Tags and Description

You can add tags and descriptions to your routes for better OpenAPI documentation organization.

#### Tags

Use the `tags` parameter to group your routes in the OpenAPI documentation:

```rust
#[vespera::route(get, tags = ["users"])]
pub async fn list_users() -> Json<Vec<User>> {
// ...
}

#[vespera::route(post, tags = ["users", "admin"])]
pub async fn create_user(Json(user): Json<User>) -> Json<User> {
// ...
}
```

#### Description

There are two ways to add descriptions to your routes:

**1. Using doc comments (recommended):**

Doc comments (`///`) are automatically extracted and used as the route description in OpenAPI:

```rust
/// Get all users from the database
///
/// Returns a list of all registered users.
#[vespera::route(get)]
pub async fn list_users() -> Json<Vec<User>> {
// ...
}
```

**2. Using the `description` parameter:**

You can also explicitly set the description using the `description` parameter. This takes priority over doc comments:

```rust
/// This doc comment will be ignored
#[vespera::route(get, description = "Custom description for OpenAPI")]
pub async fn list_users() -> Json<Vec<User>> {
// ...
}
```

#### Combined Example

```rust
/// Get user by ID
///
/// Retrieves a specific user by their unique identifier.
#[vespera::route(get, path = "/{id}", tags = ["users"])]
pub async fn get_user(Path(id): Path<u32>) -> Json<User> {
// ...
}

#[vespera::route(post, tags = ["users", "admin"], description = "Create a new user account")]
pub async fn create_user(Json(user): Json<User>) -> Json<User> {
// ...
}
```

### Supported HTTP Methods

- `GET`
Expand Down
36 changes: 36 additions & 0 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,42 @@ pub async fn create_user(Json(user): Json<User>) -> Json<User> {
}
```

### 4. Tags and Description

Add tags to group routes and descriptions for OpenAPI documentation.

**Tags:** Use the `tags` parameter to group routes.

```rust
#[vespera::route(get, tags = ["users"])]
pub async fn list_users() -> Json<Vec<User>> { ... }

#[vespera::route(post, tags = ["users", "admin"])]
pub async fn create_user(Json(user): Json<User>) -> Json<User> { ... }
```

**Description:** Two ways to add descriptions:

1. **Doc comments (recommended):** Automatically extracted from `///` comments.
```rust
/// Get all users from the database
#[vespera::route(get)]
pub async fn list_users() -> Json<Vec<User>> { ... }
```

2. **Explicit `description` parameter:** Takes priority over doc comments.
```rust
#[vespera::route(get, description = "Custom description")]
pub async fn list_users() -> Json<Vec<User>> { ... }
```

**Combined example:**
```rust
/// Get user by ID
#[vespera::route(get, path = "/{id}", tags = ["users"])]
pub async fn get_user(Path(id): Path<u32>) -> Json<User> { ... }
```

### 5. Error Handling
Vespera supports `Result<T, E>` return types. It automatically documents both the success capability (200 OK) and the error responses in the OpenAPI spec.

Expand Down
8 changes: 8 additions & 0 deletions crates/vespera_macro/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub struct RouteArgs {
pub path: Option<syn::LitStr>,
pub error_status: Option<syn::ExprArray>,
pub tags: Option<syn::ExprArray>,
pub description: Option<syn::LitStr>,
}

impl syn::parse::Parse for RouteArgs {
Expand All @@ -11,6 +12,7 @@ impl syn::parse::Parse for RouteArgs {
let mut path: Option<syn::LitStr> = None;
let mut error_status: Option<syn::ExprArray> = None;
let mut tags: Option<syn::ExprArray> = None;
let mut description: Option<syn::LitStr> = None;

// Parse comma-separated list of arguments
while !input.is_empty() {
Expand Down Expand Up @@ -39,6 +41,11 @@ impl syn::parse::Parse for RouteArgs {
let array: syn::ExprArray = input.parse()?;
tags = Some(array);
}
"description" => {
input.parse::<syn::Token![=]>()?;
let lit: syn::LitStr = input.parse()?;
description = Some(lit);
}
_ => {
return Err(lookahead.error());
}
Expand All @@ -60,6 +67,7 @@ impl syn::parse::Parse for RouteArgs {
path,
error_status,
tags,
description,
})
}
}
Expand Down
9 changes: 8 additions & 1 deletion crates/vespera_macro/src/collector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use crate::file_utils::{collect_files, file_to_segments};
use crate::metadata::{CollectedMetadata, RouteMetadata};
use crate::route::extract_route_info;
use crate::route::{extract_doc_comment, extract_route_info};
use anyhow::{Context, Result};
use std::path::Path;
use syn::Item;
Expand Down Expand Up @@ -61,6 +61,12 @@ pub fn collect_metadata(folder_path: &Path, folder_name: &str) -> Result<Collect
};
let route_path = route_path.replace('_', "-");

// Description priority: route attribute > doc comment
let description = route_info
.description
.clone()
.or_else(|| extract_doc_comment(&fn_item.attrs));

metadata.routes.push(RouteMetadata {
method: route_info.method,
path: route_path,
Expand All @@ -70,6 +76,7 @@ pub fn collect_metadata(folder_path: &Path, folder_name: &str) -> Result<Collect
signature: quote::quote!(#fn_item).to_string(),
error_status: route_info.error_status.clone(),
tags: route_info.tags.clone(),
description,
});
}
}
Expand Down
Loading