一键导入
salvo-openapi
Generate OpenAPI documentation automatically from Salvo handlers. Use for API documentation, Swagger UI, and API client generation.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Generate OpenAPI documentation automatically from Salvo handlers. Use for API documentation, Swagger UI, and API client generation.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Implement authentication and authorization using JWT, Basic Auth, or custom schemes. Use for securing API endpoints and user management.
Create basic Salvo web applications with handlers, routers, and server setup. Use when starting a new Salvo project or adding basic HTTP endpoints.
Implement caching strategies for improved performance. Use for reducing database load and speeding up responses.
Compress HTTP responses using gzip, brotli, zstd, or deflate. Use for reducing bandwidth and improving load times.
Limit concurrent requests to protect resources. Use for file uploads, expensive operations, and preventing resource exhaustion.
Configure Cross-Origin Resource Sharing (CORS) and security headers. Use for APIs accessed from browsers on different domains.
| name | salvo-openapi |
| description | Generate OpenAPI documentation automatically from Salvo handlers. Use for API documentation, Swagger UI, and API client generation. |
| version | 0.94.0 |
| tags | ["advanced","openapi","swagger","documentation"] |
[dependencies]
salvo = { version = "0.94.0", features = ["oapi"] }
serde = { version = "1", features = ["derive"] }
#[endpoint] vs #[handler]#[handler] is the plain Salvo handler. #[endpoint] registers the same handler
AND generates OpenAPI operation metadata. Use #[endpoint] on any route you want
documented.
Documentation is only generated for things the macro can see at compile time:
PathParam<T>, QueryParam<T, REQUIRED>, JsonBody<T>,
FormBody<T>, or a struct deriving ToParameters). req.param() / req.query() are
invisible to the generator.EndpointOutRegister. Json<T> (with
T: ToSchema), StatusCode, &'static str, String, StatusError, and
Result<T, E> where both impl EndpointOutRegister all work.BAD (nothing documented):
#[endpoint]
async fn get_user(req: &mut Request) -> Json<User> {
let id = req.param::<i64>("id").unwrap();
let page = req.query::<i32>("page");
Json(User { /* ... */ })
}
GOOD (params + success + error all documented):
#[endpoint]
async fn get_user(
id: PathParam<i64>,
page: QueryParam<i32, false>,
) -> Result<Json<User>, StatusError> { /* ... */ }
use salvo::oapi::extract::*;
use salvo::prelude::*;
#[endpoint]
async fn hello(name: QueryParam<String, false>) -> String {
format!("Hello, {}!", name.as_deref().unwrap_or("World"))
}
#[tokio::main]
async fn main() {
let router = Router::new().push(Router::with_path("hello").get(hello));
let doc = OpenApi::new("My API", "1.0.0").merge_router(&router);
let router = router
.unshift(doc.into_router("/api-doc/openapi.json"))
.unshift(SwaggerUi::new("/api-doc/openapi.json").into_router("/swagger-ui"));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
PathParam<T> is always required. QueryParam<T, REQUIRED> takes a const bool
(defaults to true). Call .into_inner() (or 0 for PathParam) to get the value.
use salvo::oapi::extract::{PathParam, QueryParam, JsonBody};
#[endpoint]
async fn get_user(id: PathParam<i64>) -> String {
format!("User ID: {}", id.into_inner())
}
#[endpoint]
async fn search(q: QueryParam<String, false>) -> String {
format!("Search: {}", q.as_deref().unwrap_or(""))
}
#[endpoint]
async fn create_user(user: JsonBody<CreateUser>) -> StatusCode {
let _u = user.into_inner();
StatusCode::CREATED
}
ToSchemaDoc comments on fields become descriptions. Validation and examples use the
#[salvo(schema(...))] attribute.
use salvo::oapi::ToSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, ToSchema)]
#[salvo(schema(example = json!({"id": 1, "name": "John", "email": "john@example.com"})))]
struct User {
id: i64,
/// User's full name
#[salvo(schema(pattern = "^[a-zA-Z ]+$"))]
name: String,
#[salvo(schema(format = "email"))]
email: String,
#[salvo(schema(minimum = 1, maximum = 150))]
age: Option<u8>,
}
ToParametersToParameters defaults generated struct fields to query parameters in Salvo
0.94, and path parameters are always serialized as required: true to match
OpenAPI 3.1.
use salvo::oapi::ToParameters;
use serde::Deserialize;
#[derive(Deserialize, ToParameters)]
struct Pagination {
/// Page number
#[salvo(parameter(default = 1, minimum = 1))]
page: Option<u32>,
/// Items per page
#[salvo(parameter(default = 20, minimum = 1, maximum = 100))]
per_page: Option<u32>,
}
#[endpoint]
async fn list_users(pagination: Pagination) -> Json<Vec<User>> {
Json(vec![])
}
Supported inside #[endpoint(...)]: operation_id, tags, summary, description,
request_body, responses, status_codes, parameters, security, deprecated.
#[derive(Serialize, ToSchema)]
struct ErrorResponse { message: String }
#[endpoint(
tags("users"),
summary = "Get user by ID",
description = "Returns a single user.",
status_codes(200, 404),
responses(
(status_code = 200, description = "User found", body = User),
(status_code = 404, description = "Not found", body = ErrorResponse),
)
)]
async fn get_user(id: PathParam<i64>) -> Result<Json<User>, StatusError> {
Ok(Json(User { /* ... */ }))
}
status_codes(...) filters the auto-generated responses to just the listed codes.
responses(...) adds explicit entries with custom schemas.
Salvo 0.94 emits OpenAPI 3.1 jsonSchemaDialect for the top-level schema
dialect. Routes without an HTTP method filter are skipped during OpenAPI
generation instead of being expanded to every method.
OpenApi itself only exposes info(), servers(), security(),
add_security_scheme(), tags(), and merge_router(). There is no
description() / contact_name() / license_name() on OpenApi — set them on
Info/Contact/License and pass via .info(...).
use salvo::oapi::{OpenApi, Info, Contact, License};
let doc = OpenApi::new("My API", "1.0.0")
.info(
Info::new("My API", "1.0.0")
.description("A comprehensive user management API")
.contact(Contact::new().name("API Support").email("support@example.com"))
.license(License::new("MIT")),
)
.merge_router(&router);
When constructing parameters manually, use Parameter::location(...).
Parameter::parameter_in(...) is deprecated in Salvo 0.94.
let router = router
.unshift(doc.into_router("/api-doc/openapi.json"))
.unshift(SwaggerUi::new("/api-doc/openapi.json").into_router("/swagger-ui"));
use salvo::oapi::security::{Http, HttpAuthScheme, SecurityScheme};
let doc = OpenApi::new("My API", "1.0.0")
.info(Info::new("My API", "1.0.0").description("API with authentication"))
.add_security_scheme(
"bearer_auth",
SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)),
)
.merge_router(&router);
#[endpoint(security(("bearer_auth" = [])))]
async fn get_profile() -> &'static str { "Protected profile" }
#[derive(Serialize, ToSchema)]
struct UploadResponse { filename: String, size: u64 }
#[endpoint(tags("files"), request_body(content = "multipart/form-data"))]
async fn upload_file(req: &mut Request) -> Result<Json<UploadResponse>, StatusError> {
let file = req.file("file").await.ok_or_else(StatusError::bad_request)?;
Ok(Json(UploadResponse {
filename: file.name().unwrap_or("unnamed").to_string(),
size: file.size(),
}))
}
Because the body is raw multipart, the handler takes &mut Request directly. The
request_body attribute supplies the missing OpenAPI shape.
Implement Writer + EndpointOutRegister to document a custom error variant:
use salvo::oapi::{Components, EndpointOutRegister, Operation, ToSchema};
use salvo::prelude::*;
use serde::Serialize;
#[derive(Debug, Serialize, ToSchema)]
struct ApiError { code: i32, message: String }
impl Writer for ApiError {
async fn write(self, _req: &mut Request, _depot: &mut Depot, res: &mut Response) {
res.status_code(StatusCode::BAD_REQUEST);
res.render(Json(self));
}
}
impl EndpointOutRegister for ApiError {
fn register(components: &mut Components, operation: &mut Operation) {
operation.responses.insert(
"400".into(),
salvo::oapi::Response::new("Bad Request")
.add_content("application/json", Self::to_schema(components)),
);
}
}
#[endpoint]
async fn create_user(body: JsonBody<CreateUser>) -> Result<Json<User>, ApiError> {
/* ... */
}