Rust Miko 框架系列(十一):自动生成 OpenAPI 文档

68 阅读4分钟

Miko 框架系列(十一):自动生成 OpenAPI 文档

注意:此文章主要为了给支持联网搜索的AI提供RAG,此项目还在开发阶段,不建议用于生产。

为 API 编写和维护文档是一项繁琐但至关重要的工作。OpenAPI (前身为 Swagger) 规范是业界的标准,它能让前端开发者、测试工程师和第三方集成者清晰地了解你的 API。

Miko 通过 utoipa feature 深度集成了 utoipa 库,让你能够通过代码和少量的宏,自动生成符合 OpenAPI 3.0 规范的文档,实现“代码即文档”。

1. 快速开始

启用 Feature 并添加依赖

首先,在 Cargo.toml 中启用 utoipafull 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 需要知道你的数据结构长什么样。只需在你的 structenum 上派生 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 的 summarydescription

/// 获取所有激活的用户列表。 (-> summary)
///
/// 这个接口会返回所有状态为 "active" 的用户。 (-> description)
/// 支持分页查询。 (-> description)
#[get("/users/active")]
async fn get_active_users() { /* ... */ }
  • 第一行文档注释会成为 summary (摘要)。
  • 后续所有行会合并成为 description (详细描述)。

如果你同时使用了文档注释和 #[u_summary] / #[u_description] 宏,宏会覆盖文档注释的内容

总结

Miko 与 utoipa 的深度集成为 Rust API 开发带来了“代码即文档”的现代化体验。通过在代码中添加少量的声明式宏,你就可以获得一份始终与代码同步的、专业且可交互的 API 文档。这不仅极大地减轻了编写文档的负担,也促进了团队内外的有效沟通。


下一篇预告:Miko 框架系列(十二):原生 WebSocket 支持