Swagger/OpenAPI 生成工具 for Rust: Utoipa 和 Aide
在现代 Rust Web 开发中,创建文档化的 API 并非奢侈,而是必需。Swagger (OpenAPI) 能够自动生成交互式文档、客户端 SDK 和测试控制台。在 Rust 生态系统中,有两个工具占据领先地位:Utoipa(基于宏)和 Aide(侧重于类型化和手动控制)。让我们详细了解一下它们。
Utoipa — 用于 OpenAPI 3.0 的宏
Utoipa 是一个使用属性宏 (#[utoipa::path]) 来自动生成 OpenAPI 规范的库。它与流行的框架紧密集成:Actix-web、Axum、Rocket、Warp。
特点:
- 完全支持 OpenAPI 3.0(路径、组件、安全方案)。
- 从 Rust 结构体中自动提取类型(使用
#[derive(ToSchema)])。 - 生成用于 Swagger UI 的 JSON/YAML 文件。
- 开箱即用地支持
actix-web(通过utoipa-swagger-ui)。 - 入门门槛低——只需添加宏即可。
使用 Actix-web 的示例:
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};use serde::{Deserialize, Serialize};use utoipa::{OpenApi, ToSchema};use utoipa_swagger_ui::SwaggerUi;
#[derive(Serialize, Deserialize, ToSchema)]struct User { id: u32, name: String,}
#[utoipa::path( get, path = "/users/{id}", responses( (status = 200, description = "User found", body = User), (status = 404, description = "User not found") ), params( ("id" = u32, Path, description = "User ID") ))]#[get("/users/{id}")]async fn get_user(id: web::Path<u32>) -> impl Responder { HttpResponse::Ok().json(User { id: id.into_inner(), name: "Alice".into(), })}
#[derive(OpenApi)]#[openapi(paths(get_user), components(schemas(User)))]struct ApiDoc;
#[actix_web::main]async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .service(get_user) .service( SwaggerUi::new("/swagger-ui/{_:.*}") .url("/api-doc/openapi.json", ApiDoc::openapi()), ) }) .bind("127.0.0.1:8080")? .run() .await}Aide — 控制与类型化
Aide(前身为 paperclip)提供了另一种方法:你通过结构体和函数显式描述规范,而不是通过宏。这提供了更多控制,但需要更多代码。该库支持 Axum 和 Actix-web。
特点:
- 纯粹的类型化——所有描述都通过枚举和结构体定义。
- 生成时内置的模式验证。
- 支持嵌套模型和组合(allOf, oneOf)。
- 灵活性:可以覆盖规范的任何方面。
使用 Axum 的示例:
use aide::{axum::ApiRouter, openapi::OpenApi};use axum::{routing::get, Json, Router};use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]struct Pet { id: u32, name: String,}
async fn list_pets() -> Json<Vec<Pet>> { Json(vec![Pet { id: 1, name: "Rex".into(), }])}
async fn api_docs() -> Json<OpenApi> { let mut api = OpenApi::default(); api.info.title = "Pet Store API".into(); api.info.version = "1.0.0".into(); Json(api)}
#[tokio::main]async fn main() { let app = ApiRouter::new() .route("/pets", get(list_pets)) .route("/api-doc", get(api_docs));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap();}