mcpField
U
utoipa-axum

utoipa-axum

github.com/suryavirkapur开发166 次安装41 周前更新
代码仓库

关于 utoipa-axum

utoipa-axum 是用于为 Axum 构建的 Rust REST API 编写文档的代码优先技能,介绍如何用 utoipa 派生 OpenAPI 3.1 模式与路径,并通过 Scalar 提供交互式文档。

主要功能

  1. 01使用 ToSchema 派生模式
  2. 02为处理器标注路径元数据
  3. 03组合路径与模式生成 OpenAPI
  4. 04描述查询参数和枚举
  5. 05通过 Scalar 提供交互式文档

适用场景

  1. 01记录 Axum 请求与响应类型
  2. 02根据处理器生成 OpenAPI 规范
  3. 03提供交互式 API 文档

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/suryavirkapur/skills --skill utoipa-axum

安装后在 Claude Code 中输入/utoipa-axum确认已加载。

附带文件references/api_reference.mdreferences/patterns.md

详细介绍

使用 utoipa + Axum 编写 OpenAPI

utoipa 为 Rust REST API 提供编译时 OpenAPI 文档。采用代码优先的方式:为处理器和类型添加注解,即可自动生成 OpenAPI 3.1 规范。

核心概念

  1. ToSchema — 为请求/响应体类型派生
  2. #[utoipa::path] — 为处理器添加路径、方法、参数和响应注解
  3. OpenApi — 将所有路径和架构合并为规范
  4. Scalar — 提供交互式 API 文档

Cargo.toml 配置

[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
uuid = { version = "1", features = ["serde", "v4"] }
chrono = { version = "0.4", features = ["serde"] }

# utoipa with axum integration
utoipa = { version = "5", features = ["axum_extras", "uuid", "chrono"] }
utoipa-scalar = { version = "0.3", features = ["axum"] }

ToSchema — 请求/响应类型

use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;

#[derive(Debug, Deserialize, ToSchema)]
pub struct CreateUserRequest {
    /// User's email address
    pub email: String,
    /// Password (min 8 characters)
    #[schema(min_length = 8)]
    pub password: String,
}

#[derive(Debug, Serialize, ToSchema)]
pub struct UserResponse {
    pub id: Uuid,
    pub email: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
}

#[derive(Debug, Serialize, ToSchema)]
pub struct ErrorResponse {
    pub error: String,
}

#[utoipa::path] — 为处理器编写文档

use axum::{extract::{Path, State}, http::StatusCode, Json};

#[utoipa::path(
    get,
    path = "/api/users/{id}",
    tag = "users",
    params(("id" = Uuid, Path, description = "User ID")),
    responses(
        (status = 200, description = "User found", body = UserResponse),
        (status = 404, description = "Not found", body = ErrorResponse)
    ),
    security(("bearer" = []))
)]
pub async fn get_user(
    State(state): State<AppState>,
    Path(id): Path<Uuid>,
) -> Result<Json<UserResponse>, (StatusCode, Json<ErrorResponse>)> {
    // handler logic
}

#[utoipa::path(
    post,
    path = "/api/users",
    tag = "users",
    request_body = CreateUserRequest,
    responses(
        (status = 201, description = "Created", body = UserResponse),
        (status = 400, description = "Bad request", body = ErrorResponse)
    )
)]
pub async fn create_user(
    State(state): State<AppState>,
    Json(req): Json<CreateUserRequest>,
) -> Result<(StatusCode, Json<UserResponse>), (StatusCode, Json<ErrorResponse>)> {
    // handler logic
}

OpenApi — 合并所有内容

use utoipa::openapi::security::{Http, HttpAuthScheme, SecurityScheme};
use utoipa::{Modify, OpenApi};

#[derive(OpenApi)]
#[openapi(
    info(
        title = "My API",
        version = "1.0.0",
        description = "API description"
    ),
    tags(
        (name = "users", description = "User management"),
        (name = "health", description = "Health checks")
    ),
    paths(
        get_user,
        create_user,
        health_check
    ),
    components(
        schemas(
            CreateUserRequest,
            UserResponse,
            ErrorResponse
        )
    ),
    modifiers(&SecurityAddon)
)]
pub struct ApiDoc;

struct SecurityAddon;

impl Modify for SecurityAddon {
    fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) {
        if let Some(components) = openapi.components.as_mut() {
            components.add_security_scheme(
                "bearer",
                SecurityScheme::Http(Http::new(HttpAuthScheme::Bearer)),
            );
        }
    }
}

提供 Scalar UI

use axum::{routing::get, Router};
use utoipa::OpenApi;
use utoipa_scalar::{Scalar, Servable};

pub fn create_router(state: AppState) -> Router {
    Router::new()
        .route("/health", get(health_check))
        .route("/api/users", get(list_users).post(create_user))
        .route("/api/users/{id}", get(get_user).put(update_user).delete(delete_user))
        // Scalar UI at /api/docs
        .merge(Scalar::with_url("/api/docs", ApiDoc::openapi()))
        .with_state(state)
}

IntoParams — 查询参数

use utoipa::IntoParams;

#[derive(Debug, Deserialize, IntoParams)]
pub struct PaginationParams {
    /// Page number (1-indexed)
    #[param(minimum = 1, default = 1)]
    pub page: Option<u32>,
    /// Items per page
    #[param(minimum = 1, maximum = 100, default = 20)]
    pub per_page: Option<u32>,
}

#[derive(Debug, Deserialize, IntoParams)]
pub struct UserFilters {
    /// Filter by email contains
    pub email: Option<String>,
    /// Filter by status
    pub status: Option<UserStatus>,
}

#[utoipa::path(
    get,
    path = "/api/users",
    tag = "users",
    params(PaginationParams, UserFilters),
    responses((status = 200, body = Vec<UserResponse>))
)]
pub async fn list_users(
    Query(pagination): Query<PaginationParams>,
    Query(filters): Query<UserFilters>,
) -> Json<Vec<UserResponse>> {
    // handler logic
}

架构中的枚举

#[derive(Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum UserStatus {
    Active,
    Inactive,
    Pending,
}

#[derive(Debug, Serialize, Deserialize, ToSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Event {
    UserCreated { user_id: Uuid },
    UserDeleted { user_id: Uuid, reason: String },
}

常见模式

错误处理、身份验证中间件和响应类型请参阅 references/patterns.md。

API 参考

完整的 ToSchema 和路径属性选项请参阅 references/api_reference.md。

评论

支持 Markdown