anycms-core 0.5.4

A unified API response library supporting multiple Rust web frameworks
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
# 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` 中添加:

```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

#### 示例

```toml
# 默认(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]src/result.rs, [pagination.rs]src/pagination.rs)
   - 包含数据结构和构建器方法
   - 零框架依赖
   - 无论启用哪个 feature 都会编译

2. **框架层** ([frameworks/]src/frameworks/)
   - 实现框架特定的 trait
   - 基于 feature flags 条件编译
   - 相互隔离,避免冲突

3. **集成层** ([tracing.rs]src/tracing.rs, [validator.rs]src/validator.rs)
   - 可选的第三方 crate 集成
   - 通过 feature flags 按需启用

## 使用方法

### 成功响应

```rust
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();
```

### 错误响应

```rust
use anycms_core::{ApiResult, ApiError};

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

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

### 业务错误码

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

```rust
use anycms_core::ApiResult;

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

### 时间戳

```rust
use anycms_core::ApiResult;

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

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

### 验证错误

```rust
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` 响应:

```rust
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`),支持与自定义业务错误合并:

```rust
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);
    }
    // ...
}
```

### 请求追踪

```rust
use anycms_core::ApiResult;

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

### 分页

```rust
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` 快捷方法:

```rust
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

```rust
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

```rust
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()` 方法自动按响应级别记录日志:

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

### 使用方法

```rust
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` | 如内部异常、网关超时 |

日志会自动包含 `code`、`trace_id`、`biz_code` 等结构化字段。

## 请求追踪中间件

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

### 工作原理

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

### Actix-web 使用

```rust
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 使用

```rust
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` 子句:

```rust
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)
```

### 典型数据库查询场景

```rust
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 响应格式

### 成功响应(单值)

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

### 成功响应(列表 + 分页)

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

### 空成功响应

```json
{
  "success": true
}
```

### 错误响应

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

### 业务错误码响应

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

### 验证错误响应

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

### 带追踪的错误响应

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

## 示例

运行演示示例:

```bash
# 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