# 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;
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) - 使用示例