anycms-core 0.5.4

A unified API response library supporting multiple Rust web frameworks
Documentation

anycms-core

支持多种 Rust Web 框架的统一 API 响应库。

特性

  • 框架无关的核心ApiResult<T> 结构独立于任何 Web 框架工作
  • 多框架支持:内置 actix-web 和 axum 集成
  • Feature flags:按需使用 - 零未使用的依赖
  • 灵活的响应格式:支持单值、列表、分页、错误码和额外元数据
  • 字段级验证错误FieldError 支持表单验证场景
  • 业务错误码bizCode 独立于 HTTP 状态码,支持自定义业务语义
  • 响应时间戳:内置 timestamp 字段,支持自动注入当前时间
  • 请求追踪:内置 traceId 字段,方便生产环境排查
  • 序列化风格可配置:支持 camelCase(默认)和 snake_case
  • tracing 集成:自动按响应级别记录日志(4xx -> WARN, 5xx -> ERROR)

安装

Cargo.toml 中添加:

[dependencies]
anycms-core = "0.5"

Feature Flags

  • actix (默认):启用 actix-web 集成
  • axum:启用 axum 集成
  • validator:启用 validator crate 集成,自动转换验证错误
  • tracing:启用 tracing crate 集成,自动按级别记录错误日志
  • full:启用所有集成(actix + axum + validator + tracing)
  • camel-case (默认):JSON 字段使用 camelCase
  • snake-case:JSON 字段使用 snake_case

示例

# 默认(actix-web + camelCase)
anycms-core = "0.5"

# 仅 axum
anycms-core = { version = "0.5", default-features = false, features = ["axum"] }

# snake_case 风格
anycms-core = { version = "0.5", features = ["snake-case"] }

# 启用 validator 集成
anycms-core = { version = "0.5", features = ["validator"] }

# 启用 tracing 集成
anycms-core = { version = "0.5", features = ["tracing"] }

# 启用全部
anycms-core = { version = "0.5", features = ["full"] }

项目结构

src/
├── lib.rs              # 库入口,带 feature 条件导出
├── result.rs           # 核心 ApiResult<T>、错误类型、FieldError
├── pagination.rs       # 分页元数据结构
├── tracing.rs          # tracing 集成 (feature = "tracing")
├── validator.rs        # validator crate 集成 (feature = "validator")
└── frameworks/
    ├── mod.rs          # 框架模块声明
    ├── actix.rs        # actix-web 集成 (Responder trait + 中间件)
    └── axum.rs         # axum 集成 (IntoResponse trait + 中间件)

架构设计

该库采用清晰的关注点分离设计:

  1. 核心层 (result.rs, pagination.rs)

    • 包含数据结构和构建器方法
    • 零框架依赖
    • 无论启用哪个 feature 都会编译
  2. 框架层 (frameworks/)

    • 实现框架特定的 trait
    • 基于 feature flags 条件编译
    • 相互隔离,避免冲突
  3. 集成层 (tracing.rs, validator.rs)

    • 可选的第三方 crate 集成
    • 通过 feature flags 按需启用

使用方法

成功响应

use anycms_core::{ApiResult, ResultPagination};

// 单值
let result = ApiResult::value(user);

// 列表 + 分页
let result = ApiResult::list(users)
    .with_pagination(ResultPagination::new(100, 1, 10))
    .with_extra("has_more", serde_json::json!(true));

// 空响应
let result = ApiResult::<()>::ok();

错误响应

use anycms_core::{ApiResult, ApiError};

// 使用 ApiError
let result: ApiResult<User> = ApiError::not_found("用户不存在").into();

// 使用快捷方法
let result = ApiResult::<User>::fail("操作失败").with_code(500);

业务错误码

bizCode 独立于 HTTP 状态码,用于前端业务逻辑判断:

use anycms_core::ApiResult;

// HTTP 404 + 业务码 10001
let result = ApiResult::<User>::fail("用户不存在")
    .with_code(404)
    .with_biz_code(10001);

时间戳

use anycms_core::ApiResult;

// 自动注入当前时间(Unix 毫秒)
let result = ApiResult::value(user).with_current_timestamp();

// 手动指定时间戳
let result = ApiResult::ok().with_timestamp(1700000000000);

验证错误

use anycms_core::{ApiResult, FieldError};

// 一次性传入所有字段错误
let result: ApiResult<User> = ApiResult::validation_errors(vec![
    FieldError::new("email", "格式不正确"),
    FieldError::new("name", "不能为空"),
]);

// 或链式添加
let result = ApiResult::<User>::fail("验证失败")
    .with_code(422)
    .with_error("email", "格式不正确")
    .with_error("name", "不能为空");

validator crate 集成

启用 validator feature 后,可以自动将 validator::ValidationErrors 转换为 ApiResult 响应:

use anycms_core::ApiResult;
use validator::Validate;

#[derive(Validate)]
struct CreateUser {
    #[validate(email(message = "邮箱格式不正确"))]
    email: String,
    #[validate(length(min = 1, message = "用户名不能为空"))]
    name: String,
}

async fn create_user(data: CreateUser) -> ApiResult<User> {
    // validate() 返回 Err 时自动转换为 422 响应
    if let Err(errors) = data.validate() {
        return errors.into(); // From<ValidationErrors> for ApiResult<T>
    }
    // ... 业务逻辑
    ApiResult::value(user)
}

嵌套结构体的字段名会自动加前缀(如 address.city),支持与自定义业务错误合并:

use anycms_core::{validation_errors_to_field_errors, ApiResult, FieldError};

fn handle(data: CreateUser) -> ApiResult<User> {
    let mut errors: Vec<FieldError> = Vec::new();

    // 收集 validator 错误
    if let Err(ve) = data.validate() {
        errors.extend(validation_errors_to_field_errors(&ve));
    }

    // 添加自定义业务校验
    if data.name == "admin" {
        errors.push(FieldError::new("name", "用户名 'admin' 已被保留"));
    }

    if !errors.is_empty() {
        return ApiResult::validation_errors(errors);
    }
    // ...
}

请求追踪

use anycms_core::ApiResult;

let result = ApiResult::<User>::fail("用户不存在")
    .with_code(404)
    .with_trace_id("req-abc123");

分页

use anycms_core::ResultPagination;

let pagination = ResultPagination::new(95, 3, 10);
// 自动计算并输出:
// - total: 95
// - page: 3
// - pageSize: 10
// - totalPages: 10
// - hasNextPage: true
// - hasPrevPage: true

错误码

ErrorCode 覆盖常用 HTTP 状态码,每个变体都有对应的 ApiError 快捷方法:

use anycms_core::ApiError;

ApiError::bad_request("参数错误");        // 400
ApiError::unauthorized("未认证");         // 401
ApiError::forbidden("无权限");            // 403
ApiError::not_found("资源不存在");        // 404
ApiError::conflict("冲突");               // 409
ApiError::request_timeout("请求超时");    // 408
ApiError::payload_too_large("文件过大");  // 413
ApiError::too_many_requests("限流");      // 429
ApiError::validation("校验失败");         // 422
ApiError::internal("内部错误");           // 500
ApiError::gateway_timeout("网关超时");    // 504

框架集成

Actix-web

use actix_web::get;
use anycms_core::ApiResult;

#[get("/users/{id}")]
async fn get_user(path: web::Path<u32>) -> ApiResult<User> {
    match find_user(path.into_inner()).await {
        Some(user) => ApiResult::value(user),
        None => ApiResult::<User>::fail("用户不存在").with_code(404),
    }
}

Axum

use axum::extract::Path;
use anycms_core::ApiResult;

async fn get_user(Path(id): Path<u32>) -> ApiResult<User> {
    match find_user(id).await {
        Some(user) => ApiResult::value(user),
        None => ApiResult::<User>::fail("用户不存在").with_code(404),
    }
}

Tracing 集成

启用 tracing feature 后,可以通过 with_tracing() 方法自动按响应级别记录日志:

[dependencies]
anycms-core = { version = "0.5", features = ["tracing"] }

使用方法

use anycms_core::{ApiResult, ApiResultTracing};

// 成功响应:不产生任何日志(避免噪音)
let result = ApiResult::value(user).with_tracing();

// 4xx 错误:WARN 级别
let result = ApiResult::<User>::fail("用户不存在")
    .with_code(404)
    .with_tracing(); // -> tracing::warn!

// 5xx 错误:ERROR 级别
let result = ApiResult::<User>::fail("内部错误")
    .with_code(500)
    .with_tracing(); // -> tracing::error!

日志级别规则

响应状态 code 范围 日志级别 说明
成功 (success: true) - 无日志 避免噪音
客户端错误 400-499 WARN 如参数错误、权限不足
服务端错误 500+ ERROR 如内部异常、网关超时

日志会自动包含 codetrace_idbiz_code 等结构化字段。

请求追踪中间件

anycms-core 为 actix-web 和 axum 提供了中间件,自动为响应注入 traceId

工作原理

  1. 从请求头中提取 trace ID(默认检查 X-Request-IDX-Trace-ID
  2. 若请求头中不存在,则自动生成 UUID v4
  3. 将 trace ID 注入到 ApiResult 响应的 traceId 字段

Actix-web 使用

use actix_web::{web, App, HttpServer};
use anycms_core::actix::ApiResultLayer;

HttpServer::new(|| {
    App::new()
        .wrap(ApiResultLayer::default())  // 自动注入 trace_id
        .route("/users", web::get().to(list_users))
        .route("/users/{id}", web::get().to(get_user))
})

Axum 使用

use axum::{Router, routing::get, middleware};
use anycms_core::axum::api_result_middleware;

// 零配置(使用默认配置)
let app = Router::new()
    .route("/users", get(list_users))
    .layer(middleware::from_fn(api_result_middleware));

// 自定义配置
use anycms_core::axum::{ApiResultMiddlewareConfig, api_result_middleware_with_config};

let config = ApiResultMiddlewareConfig {
    trace_id_headers: vec!["x-request-id".to_string()],
    inject_timestamp: true,  // 同时自动注入 timestamp
};
let app = Router::new()
    .route("/users", get(list_users))
    .layer(middleware::from_fn_with_state(config, api_result_middleware_with_config));

ResultPagination 数据库查询辅助

ResultPagination 提供了数据库分页查询的辅助方法,可以直接用于 SQL OFFSET / LIMIT 子句:

use anycms_core::ResultPagination;

let pagination = ResultPagination::new(100, 3, 10);

// 用于 SQL OFFSET 子句: (page - 1) * page_size
let offset = pagination.offset(); // 20

// 用于 SQL LIMIT 子句: 等同于 page_size
let limit = pagination.limit();   // 10

// 页面判断
pagination.is_first_page(); // false (当前是第 3 页)
pagination.is_last_page();  // false (总共 10 页)

// 翻页
pagination.next_page();     // Some(4)
pagination.prev_page();     // Some(2)

典型数据库查询场景

use anycms_core::{ApiResult, ResultPagination};

async fn list_users(page: i64, page_size: i64) -> ApiResult<User> {
    let total = count_users().await;               // SELECT COUNT(*)
    let pagination = ResultPagination::new(total, page, page_size);

    let users = query_users(
        pagination.offset(),   // OFFSET
        pagination.limit(),    // LIMIT
    ).await;

    ApiResult::list(users).with_pagination(pagination)
}

API 响应格式

成功响应(单值)

{
  "success": true,
  "data": { "id": 1, "name": "张三" }
}

成功响应(列表 + 分页)

{
  "success": true,
  "data": [
    { "id": 1, "name": "张三" },
    { "id": 2, "name": "李四" }
  ],
  "pagination": {
    "total": 100,
    "page": 1,
    "pageSize": 10,
    "totalPages": 10,
    "hasNextPage": true,
    "hasPrevPage": false
  }
}

空成功响应

{
  "success": true
}

错误响应

{
  "success": false,
  "message": "用户不存在",
  "code": 404
}

业务错误码响应

{
  "success": false,
  "message": "用户不存在",
  "code": 404,
  "bizCode": 10001,
  "timestamp": 1780542642018
}

验证错误响应

{
  "success": false,
  "message": "Validation failed",
  "code": 422,
  "errors": [
    { "field": "email", "message": "格式不正确" },
    { "field": "name", "message": "不能为空" }
  ]
}

带追踪的错误响应

{
  "success": false,
  "message": "用户不存在",
  "code": 404,
  "traceId": "req-abc123"
}

示例

运行演示示例:

# Actix-web 演示
cargo run --example actix_demo --features actix

# Axum 演示
cargo run --example axum_demo --features axum

# 验证错误演示
cargo run --example validation_demo

# validator crate 集成演示
cargo run --example validator_demo --features validator

# 请求追踪演示
cargo run --example trace_id_demo --features actix

# 业务错误码 + 时间戳演示
cargo run --example biz_code_demo

# ResponseData From trait 演示
cargo run --example from_trait_demo

# 中间件演示 (Actix-web)
cargo run --example middleware_actix_demo --features actix

# 中间件演示 (Axum)
cargo run --example middleware_axum_demo --features axum

# tracing 集成演示
cargo run --example tracing_demo --features tracing

# 检查所有示例
cargo check --examples --all-features

许可证

MIT