anycms-core 0.5.4

A unified API response library supporting multiple Rust web frameworks
Documentation
# ResponseData 设计说明

## 概述

从 v0.4.0 开始,`anycms-core` 使用统一的 `data` 字段来处理单值和列表响应,通过 `ResponseData<T>` 枚举在类型层面区分。

## 设计动机

### 之前的问题

```rust
// 旧设计 - 存在类型冲突
pub struct ApiResult<T> {
    pub data: Option<T>,       // 单值
    pub list: Option<Vec<T>>,  // 列表 - 但 T 已经被占用!
}
```

**问题**:
1. `data: Option<T>``list: Option<Vec<T>>` 共用同一个泛型 `T`
2. 编译期无法保证互斥性,可能出现同时存在的情况
3. JSON 输出总有一个 `null` 字段

### 新的解决方案

```rust
// 新设计 - 类型安全
pub enum ResponseData<T> {
    Single(T),
    Multiple(Vec<T>),
}

pub struct ApiResult<T> {
    pub data: Option<ResponseData<T>>,
}
```

**优势**:
1. ✅ 编译期类型安全
2. ✅ 单一字段,语义清晰
3. ✅ JSON 输出简洁,无冗余
4. ✅ 前端只需判断 `Array.isArray(data)`

## JSON 输出对比

### 旧设计

```json
// 单值 - list 为 null
{
  "success": true,
  "data": { "id": 1 },
  "list": null
}

// 列表 - data 为 null
{
  "success": true,
  "data": null,
  "list": [{ "id": 1 }]
}
```

### 新设计

```json
// 单值
{
  "success": true,
  "data": { "id": 1 }
}

// 列表
{
  "success": true,
  "data": [{ "id": 1 }]
}
```

## API 使用

### 创建响应

```rust
use anycms_core::ApiResult;

// 单值响应
let result: ApiResult<User> = ApiResult::value(user);

// 列表响应
let result: ApiResult<User> = ApiResult::list(users);

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

// 错误响应
let result: ApiResult<User> = ApiResult::failure("Not found").with_code(404);
```

### 访问数据

```rust
// 检查数据类型
if let Some(data) = &result.data {
    if data.is_single() {
        // 处理单值
        if let Some(user) = data.as_single() {
            println!("User: {}", user.name);
        }
    } else if data.is_multiple() {
        // 处理列表
        if let Some(users) = data.as_multiple() {
            println!("Total: {}", users.len());
        }
    }
}
```

### 前端使用

```typescript
// TypeScript 类型定义
interface ApiResult<T> {
  success: boolean;
  data?: T | T[];
  message?: string;
  code?: number;
}

// 使用
function handleResponse(response: ApiResult<User>) {
  if (response.success) {
    if (Array.isArray(response.data)) {
      // 列表
      response.data.forEach(user => console.log(user.name));
    } else if (response.data) {
      // 单值
      console.log(response.data.name);
    }
  }
}
```

## 迁移指南

### 后端(Rust)

```rust
// 旧代码
let result = ApiResult {
    success: true,
    data: Some(user),
    list: None,
    // ...
};

// 新代码
let result = ApiResult::value(user);
```

### 前端

```javascript
// 旧代码
if (response.data) {
    // 处理单值
} else if (response.list) {
    // 处理列表
}

// 新代码
if (Array.isArray(response.data)) {
    // 处理列表
} else if (response.data) {
    // 处理单值
}
```

## 技术细节

### Serde 序列化

`ResponseData` 使用 `#[serde(untagged)]` 实现直接序列化:

```rust
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum ResponseData<T> {
    Single(T),
    Multiple(Vec<T>),
}
```

这样:
- `ResponseData::Single(User { id: 1 })``{"id": 1}`
- `ResponseData::Multiple(vec![User { id: 1 }])``[{"id": 1}]`

### 类型安全

```rust
// 编译期保证
let result = ApiResult::value(user);
// result.data 是 Some(ResponseData::Single(user))
// 不可能出现 ResponseData::Multiple
```

## 常见问题

### Q: 为什么不保留 `data``list` 两个字段?

A:
1. 类型不安全:`data: Option<T>``list: Option<Vec<T>>` 冲突
2. 冗余字段:总有一个是 `null`
3. 维护成本:需要文档和注释说明互斥规则

### Q: 前端如何区分单值和列表?

A: 使用 `Array.isArray()`:

```javascript
if (Array.isArray(response.data)) {
    // 列表
} else {
    // 单值
}
```

### Q: 这是一个破坏性变更吗?

A: 是的,但影响最小:
- JSON 格式更简洁(移除了 `null` 字段)
- 前端只需修改字段判断逻辑
- 后端 API 保持不变(`ApiResult::value()``ApiResult::list()`
## 参考

- [src/result.rs]../src/result.rs - 核心实现
- [tests/response_data_test.rs]../tests/response_data_test.rs - 测试用例
- [examples/response_data_demo.rs]../examples/response_data_demo.rs - 使用示例