Miko 框架系列(十一):自动生成 OpenAPI 文档
注意:此文章主要为了给支持联网搜索的AI提供RAG,此项目还在开发阶段,不建议用于生产。
为 API 编写和维护文档是一项繁琐但至关重要的工作。OpenAPI (前身为 Swagger) 规范是业界的标准,它能让前端开发者、测试工程师和第三方集成者清晰地了解你的 API。
Miko 通过 utoipa feature 深度集成了 utoipa 库,让你能够通过代码和少量的宏,自动生成符合 OpenAPI 3.0 规范的文档,实现“代码即文档”。
1. 快速开始
启用 Feature 并添加依赖
首先,在 Cargo.toml 中启用 utoipa 或 full feature。同时,为了获得交互式 UI,我们添加 utoipa-scalar 作为开发依赖。
[dependencies]
# 启用 full feature 会自动包含 utoipa
miko = { version = "0.3", features = ["full"] }
[dev-dependencies]
# Scalar 是一个漂亮的 OpenAPI UI
utoipa-scalar = { version = "0.2", features = ["axum"] }
步骤 2: 为数据结构派生 ToSchema
utoipa 需要知道你的数据结构长什么样。只需在你的 struct 或 enum 上派生 ToSchema 即可。
use miko::{*, macros::*, utoipa, OpenApi, ToSchema};
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, ToSchema)]
struct User {
#[schema(example = 1)]
id: u32,
#[schema(example = "Alice")]
name: String,
}
步骤 3: 装饰你的处理器
Miko 的路由宏(如 #[get])会自动从你的代码中推断一部分 OpenAPI 信息,但有些信息需要你手动提供。
- 自动推断:
- 路径参数 (
#[path]) - 查询参数 (
Query<T>) - 请求体 (
Json<T>) - API 描述 (从
///文档注释中提取)
- 路径参数 (
- 需要手动标注:
- 响应体 (
#[u_response]): 这是必须的,因为 Miko 无法从impl IntoResponse的返回类型中推断出具体的成功响应模型。 - API 标签 (
#[u_tag]):用于在 UI 中对 API 进行分组。
- 响应体 (
/// 获取用户信息
///
/// 根据用户 ID 查询并返回用户的详细信息。
#[get("/users/{id}")]
#[u_tag("用户管理")] // API 分组标签
#[u_response(status = 200, description = "成功获取用户", body = User)] // 成功响应
#[u_response(status = 404, description = "用户未找到")] // 失败响应
async fn get_user(
#[path] #[desc("用户的唯一标识符")] id: u32, // 使用 #[desc] 为参数添加描述
) -> AppResult<Json<User>> {
// ... 业务逻辑 ...
let user = find_user(id).ok_or(AppError::NotFound(...))?;
Ok(Json(user))
}
步骤 4: 定义 OpenAPI Doc 并提供服务
最后,你需要定义一个 OpenApi 根结构,并创建两个路由:一个用于提供原始的 openapi.json,另一个用于展示交互式的 UI。
use utoipa_scalar::{Scalar, Servable};
// 定义 OpenAPI 文档的元信息
#[derive(OpenApi)]
#[openapi(
info(
title = "My App API",
version = "1.0.0",
description = "API documentation for My App"
),
// utoipa 会自动发现所有被 `#[utoipa::path]` 装饰的路由
// 而 Miko 的路由宏会自动为你添加 `#[utoipa::path]`
// 所以这里通常不需要手动列出 paths(最近发现好像还是得新注册,在考虑开发一个简化的)
tags(
(name = "用户管理", description = "用户相关的 API")
)
)]
struct ApiDoc;
// 提供 openapi.json 文件的路由
#[route("/openapi.json", method = "get")]
async fn openapi_json() -> Json<utoipa::openapi::OpenApi> {
Json(ApiDoc::openapi())
}
// 提供 Scalar UI 界面的路由
#[route("/scalar", method = "get")]
async fn scalar_ui() -> impl IntoResponse {
Scalar::new("/openapi.json").into_response()
}
#[miko]
async fn main() {
println!("📚 Scalar UI available at: http://localhost:8080/scalar");
}
现在,运行你的应用并访问 http://localhost:8080/scalar,你将看到一个漂亮的、可交互的 API 文档界面!
2. Miko 的 OpenAPI 宏
为了简化 utoipa 的使用,Miko 提供了一系列以 #[u_] 开头的宏,它们是 #[utoipa::path(...)] 属性的便捷别名。
| Miko 宏 | utoipa 等价物 | 用途 |
|---|---|---|
#[u_tag("...")] | #[utoipa::path(tag = "...")] | 设置 API 标签 |
#[u_response(...)] | #[utoipa::path(responses(...))] | 定义响应 |
#[u_summary("...")] | #[utoipa::path(summary = "...")] | 设置 API 摘要 |
#[u_description("...")] | #[utoipa::path(description = "...")] | 设置 API 详细描述 |
#[u_request_body(...)] | #[utoipa::path(request_body = ...)] | 自定义请求体 |
#[u_param(...)] | #[utoipa::path(params(...))] | 补充参数信息 |
#[u_deprecated] | #[utoipa::path(deprecated = true)] | 标记 API 已废弃 |
此外,还有一个特殊的 #[desc("...")] 宏,用于为提取器参数(如 #[path] 或 Query)添加描述。
3. 文档注释的自动提取
Miko 会自动将你为处理器函数编写的 /// 文档注释转换为 OpenAPI 的 summary 和 description。
/// 获取所有激活的用户列表。 (-> summary)
///
/// 这个接口会返回所有状态为 "active" 的用户。 (-> description)
/// 支持分页查询。 (-> description)
#[get("/users/active")]
async fn get_active_users() { /* ... */ }
- 第一行文档注释会成为
summary(摘要)。 - 后续所有行会合并成为
description(详细描述)。
如果你同时使用了文档注释和 #[u_summary] / #[u_description] 宏,宏会覆盖文档注释的内容。
总结
Miko 与 utoipa 的深度集成为 Rust API 开发带来了“代码即文档”的现代化体验。通过在代码中添加少量的声明式宏,你就可以获得一份始终与代码同步的、专业且可交互的 API 文档。这不仅极大地减轻了编写文档的负担,也促进了团队内外的有效沟通。