rust有没有go swag的功能
2026/8/6 9:45:43 网站建设 项目流程

Rust 生态中,与 Go Swag 功能最接近、也最主流的方案是utoipa

它们都是“代码优先”的 OpenAPI 文档生成工具,核心思路一致:通过注解/宏从源码生成文档,而非手动编写 YAML/JSON 文件

🆚 与 Go Swag 的对比

虽然功能相似,但 Rust 的实现更深度地融入了语言特性:

  • 核心机制:Go Swag 通过// @Summary等注释解析;utoipa则通过#[utoipa::path]过程宏在编译期处理,与 Rust 类型系统结合更紧密。

  • 生态集成utoipa支持 Actix Web、Axum、Rocket 等主流 Rust 框架,而 Go Swag 主要面向 Gin、Echo 等。

  • 类型驱动:配合schemars库,能自动推导结构体 Schema,大幅减少手写参数/响应模型的繁琐工作。

🦀 Rust 主流方案:utoipa

这是目前社区的事实标准,在 GitHub 上活跃度高,成熟度也最好。

基本用法示例:

  1. 标注结构体:用#[derive(ToSchema)]为数据模型生成文档。

  2. 标注接口:用#[utoipa::path(...)]宏描述接口的路径、参数和响应。

  3. 聚合生成:创建一个#[derive(OpenApi)]的结构体,引入所有接口路径,即可生成完整的 OpenAPI 规范。

⚙️ 其他相关工具

  • rocket_okapi/rocket-autodocu:如果你使用Rocket框架,这两个库提供了更专门的集成方案。

  • oas3-gen:这是一个方向相反的工具,它从 OpenAPI 文件生成 Rust 代码,适合“契约优先”的开发模式。

🧐 我能用它做什么?

  • 自动生成:稳定的机器可读 API 文档(openapi.json)。

  • 交互式 UI:集成 Swagger UI 或 RapiDoc 等可视化界面,方便调试。

  • 保持同步:代码改,文档就改,彻底告别“文档和代码不一致”的烦恼。

你是用哪个 Web 框架(比如 Actix、Axum 或 Rocket)?告诉我具体框架,我可以给你写一个最简的utoipa集成示例。

一个完整的utoipa+axum示例,包含完整的项目结构和实际可运行的代码。

完整项目结构

text

utoipa-demo/ ├── Cargo.toml └── src/ └── main.rs

1.Cargo.toml

toml

[package] name = "utoipa-demo" version = "0.1.0" edition = "2021" [dependencies] axum = "0.7" tokio = { version = "1", features = ["full"] } serde = { version = "1", features = ["derive"] } utoipa = { version = "4", features = ["axum_extras"] } utoipa-swagger-ui = { version = "4", features = ["axum"] } tracing = "0.1" tracing-subscriber = "0.3"

2.src/main.rs- 完整代码

rust

use axum::{ extract::{Path, Query, State}, http::StatusCode, response::IntoResponse, routing::{get, post}, Json, Router, }; use serde::{Deserialize, Serialize}; use std::sync::Arc; use utoipa::{OpenApi, ToSchema}; use utoipa_swagger_ui::SwaggerUi; // ========== 数据模型 ========== #[derive(Debug, Serialize, Deserialize, ToSchema, Clone)] pub struct User { /// 用户ID pub id: u64, /// 用户名 pub username: String, /// 邮箱地址 pub email: String, /// 年龄(可选) pub age: Option<u32>, } #[derive(Debug, Deserialize, ToSchema)] pub struct CreateUserRequest { /// 用户名(必填) pub username: String, /// 邮箱地址(必填) pub email: String, /// 年龄(可选) pub age: Option<u32>, } #[derive(Debug, Deserialize, ToSchema)] pub struct UpdateUserRequest { /// 用户名 pub username: Option<String>, /// 邮箱 pub email: Option<String>, /// 年龄 pub age: Option<u32>, } #[derive(Debug, Serialize, Deserialize, ToSchema)] pub struct UserListResponse { /// 用户列表 pub users: Vec<User>, /// 总数量 pub total: u64, } #[derive(Debug, Deserialize, ToSchema)] pub struct QueryParams { /// 分页偏移量 pub offset: Option<u64>, /// 分页限制 pub limit: Option<u64>, } // ========== 错误响应 ========== #[derive(Debug, Serialize, Deserialize, ToSchema)] pub struct ErrorResponse { pub code: u16, pub message: String, } // ========== 应用状态 ========== type AppState = Arc<Vec<User>>; // ========== API 处理器 ========== /// 获取所有用户 #[utoipa::path( get, path = "/api/users", params(QueryParams), responses( (status = 200, description = "获取用户列表成功", body = UserListResponse), (status = 500, description = "服务器内部错误", body = ErrorResponse) ), tag = "user" )] async fn get_users( State(state): State<AppState>, Query(params): Query<QueryParams>, ) -> impl IntoResponse { let offset = params.offset.unwrap_or(0) as usize; let limit = params.limit.unwrap_or(10) as usize; let users: Vec<User> = state .iter() .skip(offset) .take(limit) .cloned() .collect(); let response = UserListResponse { users, total: state.len() as u64, }; (StatusCode::OK, Json(response)) } /// 获取单个用户 #[utoipa::path( get, path = "/api/users/{id}", params( ("id" = u64, Path, description = "用户ID") ), responses( (status = 200, description = "获取用户成功", body = User), (status = 404, description = "用户不存在", body = ErrorResponse), (status = 500, description = "服务器内部错误", body = ErrorResponse) ), tag = "user" )] async fn get_user( State(state): State<AppState>, Path(id): Path<u64>, ) -> impl IntoResponse { match state.iter().find(|user| user.id == id) { Some(user) => (StatusCode::OK, Json(user.clone())).into_response(), None => { let error = ErrorResponse { code: 404, message: format!("用户 {} 不存在", id), }; (StatusCode::NOT_FOUND, Json(error)).into_response() } } } /// 创建新用户 #[utoipa::path( post, path = "/api/users", request_body = CreateUserRequest, responses( (status = 201, description = "创建用户成功", body = User), (status = 400, description = "请求参数错误", body = ErrorResponse), (status = 500, description = "服务器内部错误", body = ErrorResponse) ), tag = "user" )] async fn create_user( State(state): State<AppState>, Json(payload): Json<CreateUserRequest>, ) -> impl IntoResponse { // 这里简化处理,实际应该写入数据库 let new_id = state.len() as u64 + 1; let user = User { id: new_id, username: payload.username, email: payload.email, age: payload.age, }; (StatusCode::CREATED, Json(user)) } /// 更新用户信息 #[utoipa::path( put, path = "/api/users/{id}", params( ("id" = u64, Path, description = "用户ID") ), request_body = UpdateUserRequest, responses( (status = 200, description = "更新用户成功", body = User), (status = 404, description = "用户不存在", body = ErrorResponse), (status = 500, description = "服务器内部错误", body = ErrorResponse) ), tag = "user" )] async fn update_user( State(state): State<AppState>, Path(id): Path<u64>, Json(payload): Json<UpdateUserRequest>, ) -> impl IntoResponse { match state.iter().find(|user| user.id == id) { Some(existing) => { // 这里简化处理,实际应该更新数据库 let updated = User { id: existing.id, username: payload.username.unwrap_or_else(|| existing.username.clone()), email: payload.email.unwrap_or_else(|| existing.email.clone()), age: payload.age.or(existing.age), }; (StatusCode::OK, Json(updated)).into_response() } None => { let error = ErrorResponse { code: 404, message: format!("用户 {} 不存在", id), }; (StatusCode::NOT_FOUND, Json(error)).into_response() } } } /// 删除用户 #[utoipa::path( delete, path = "/api/users/{id}", params( ("id" = u64, Path, description = "用户ID") ), responses( (status = 204, description = "删除用户成功"), (status = 404, description = "用户不存在", body = ErrorResponse), (status = 500, description = "服务器内部错误", body = ErrorResponse) ), tag = "user" )] async fn delete_user( State(state): State<AppState>, Path(id): Path<u64>, ) -> impl IntoResponse { match state.iter().find(|user| user.id == id) { Some(_) => StatusCode::NO_CONTENT.into_response(), None => { let error = ErrorResponse { code: 404, message: format!("用户 {} 不存在", id), }; (StatusCode::NOT_FOUND, Json(error)).into_response() } } } // ========== OpenAPI 文档 ========== #[derive(OpenApi)] #[openapi( paths( get_users, get_user, create_user, update_user, delete_user ), components( schemas( User, CreateUserRequest, UpdateUserRequest, UserListResponse, QueryParams, ErrorResponse ) ), tags( (name = "user", description = "用户管理 API") ), info( title = "用户服务 API", description = "一个完整的用户管理 REST API 示例", version = "1.0.0", contact( name = "API 支持团队", email = "support@example.com", url = "https://example.com" ), license( name = "MIT", url = "https://opensource.org/licenses/MIT" ) ) )] struct ApiDoc; // ========== 主函数 ========== #[tokio::main] async fn main() { // 初始化日志 tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .init(); // 准备模拟数据 let users = vec![ User { id: 1, username: "alice".to_string(), email: "alice@example.com".to_string(), age: Some(25), }, User { id: 2, username: "bob".to_string(), email: "bob@example.com".to_string(), age: None, }, User { id: 3, username: "charlie".to_string(), email: "charlie@example.com".to_string(), age: Some(30), }, ]; let state = Arc::new(users); // 构建路由 let app = Router::new() // API 路由 .route("/api/users", get(get_users).post(create_user)) .route( "/api/users/{id}", get(get_user).put(update_user).delete(delete_user), ) // Swagger UI(文档界面) .merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi())) .with_state(state); let addr = "127.0.0.1:3000"; println!("🚀 服务器启动: http://{}", addr); println!("📚 Swagger UI: http://{}/swagger-ui", addr); println!("📄 OpenAPI JSON: http://{}/api-docs/openapi.json", addr); let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); axum::serve(listener, app).await.unwrap(); }

3. 运行与使用

启动服务

bash

cargo run

访问文档

  • Swagger UI:http://127.0.0.1:3000/swagger-ui

  • OpenAPI JSON:http://127.0.0.1:3000/api-docs/openapi.json

API 测试

bash

# 获取所有用户 curl http://127.0.0.1:3000/api/users # 分页查询 curl "http://127.0.0.1:3000/api/users?offset=0&limit=2" # 获取单个用户 curl http://127.0.0.1:3000/api/users/1 # 创建用户 curl -X POST http://127.0.0.1:3000/api/users \ -H "Content-Type: application/json" \ -d '{"username":"dave","email":"dave@example.com","age":28}'

4. 核心特性说明

关键宏

  • #[derive(ToSchema)]:自动生成结构体的 OpenAPI Schema

  • #[utoipa::path(...)]:标注接口,描述路径、参数、响应

  • #[derive(OpenApi)]:聚合所有接口生成完整文档

常见配置项

rust

// 更复杂的参数描述 #[utoipa::path( get, path = "/api/search", params( ("q" = String, Query, description = "搜索关键词", example = "rust"), ("page" = Option<u32>, Query, description = "页码", minimum = 1, default = 1), ("sort" = Option<String>, Query, description = "排序方式", enum = ["asc", "desc"]) ) )] // 多种响应 #[utoipa::path( post, path = "/api/users", request_body = CreateUserRequest, responses( (status = 201, description = "创建成功", body = User), (status = 400, description = "参数错误", body = ErrorResponse), (status = 409, description = "用户已存在", body = ErrorResponse) ), security( ("api_key" = []) ) )]

5. 进阶:安全认证

rust

// 在 OpenApi 结构体中添加 security 配置 #[derive(OpenApi)] #[openapi( // ... components( schemas(...), security_schemes( ("api_key" = SecurityScheme::ApiKey(ApiKey::Header("X-API-Key"))), ("jwt" = SecurityScheme::Http(Http::Bearer)) ) ) )]

6. 与其他框架集成

Actix-web版本:

rust

// Cargo.toml utoipa = { version = "4", features = ["actix_extras"] } utoipa-swagger-ui = { version = "4", features = ["actix-web"] } // main.rs use utoipa_swagger_ui::SwaggerUi; HttpServer::new(|| { A:new() .service( SwaggerUi::new("/swagger-ui/{_:.*}") .url("/api-docs/openapi.json", ApiDoc::openapi()) ) // ... 其他路由 })

这个示例已经包含了完整的 CRUD 操作和自动生成文档功能。你可以直接运行并访问 Swagger UI 进行交互式测试!🎉

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询