第一个 CRUD API
本节实现一个内存中的用户 CRUD,展示 GET/POST/PUT/DELETE 四种路由模式。
数据模型
`src/contracts/user.rs`:
use webx::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize)]
pub struct UserDto {
pub id: String,
pub name: String,
pub email: String,
}
// ── List ──
pub struct ListUsersRequest;
#[get("/api/users")]
impl IRequest<Vec<UserDto>> for ListUsersRequest {}
// ── Get by ID ──
pub struct GetUserRequest {
pub id: String,
}
#[get("/api/users/{id}")]
impl IRequest<UserDto> for GetUserRequest {}
// ── Create ──
#[derive(Deserialize)]
pub struct CreateUserRequest {
pub name: String,
pub email: String,
}
#[post("/api/users")]
impl IRequest<UserDto> for CreateUserRequest {}
// ── Update ──
#[derive(Deserialize)]
pub struct UpdateUserRequest {
pub id: String,
pub name: String,
pub email: String,
}
#[put("/api/users/{id}")]
impl IRequest<UserDto> for UpdateUserRequest {}
// ── Delete ──
pub struct DeleteUserRequest {
pub id: String,
}
#[delete("/api/users/{id}")]
impl IRequest<()> for DeleteUserRequest {}
共享存储
`src/handlers/user.rs`:
use std::collections::HashMap;
use std::sync::Arc;
use webx::*;
use tokio::sync::RwLock;
use crate::contracts::user::*;
type UserStore = Arc<RwLock<HashMap<String, UserDto>>>;
// ── List ──
#[derive(Default)]
struct ListUsersHandler;
#[handler]
#[async_trait]
impl IRequestHandler<ListUsersRequest, Vec<UserDto>> for ListUsersHandler {
async fn handle(&self, _req: ListUsersRequest) -> Result<Vec<UserDto>> {
// 实际项目中通过 DI 注入 store
Ok(vec![])
}
}
带依赖注入的完整版本:
struct CreateUserHandler {
store: UserStore,
}
#[async_trait]
impl IRequestHandler<CreateUserRequest, UserDto> for CreateUserHandler {
async fn handle(&self, req: CreateUserRequest) -> Result<UserDto> {
let id = uuid::Uuid::new_v4().to_string();
let user = UserDto {
id: id.clone(),
name: req.name,
email: req.email,
};
self.store.write().await.insert(id, user.clone());
Ok(user)
}
}
注册带依赖的 Handler
在 `main.rs` 中:
#[tokio::main]
async fn main() {
let store: UserStore = Arc::new(RwLock::new(HashMap::new()));
Host::builder()
.register(move |svc| {
let store = Arc::clone(&store);
svc.singleton::<dyn IRequestHandler<CreateUserRequest, UserDto>>(
move |_| Arc::new(CreateUserHandler { store: Arc::clone(&store) })
);
// 其他 Handler 同理...
})
.build()
.run()
.await
.expect("Server failed");
}
HTTP 端点应使用 `#[handler]` / `#[handler(inject)]`(inventory 自动注册)。以下 **`register_handlers!` 已弃用**,仅适用于非 HTTP 的 Mediator 场景:
.register(|svc| {
register_handlers!(svc,
ListUsersRequest => Vec<UserDto> => ListUsersHandler,
GetUserRequest => UserDto => GetUserHandler,
)
})
手动 `.singleton::<dyn IRequestHandler<…>>()` 同理:HTTP `RouteDispatch` 不经过 DI 查找 Handler,请勿将其作为主路径。
测试 API
# 创建用户
curl -X POST http://localhost:5000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com"}'
# 获取列表
curl http://localhost:5000/api/users
# 获取单个
curl http://localhost:5000/api/users/{id}
# 更新
curl -X PUT http://localhost:5000/api/users/{id} \
-H "Content-Type: application/json" \
-d '{"name":"Alice Updated","email":"alice@example.com"}'
# 删除
curl -X DELETE http://localhost:5000/api/users/{id}
错误处理示例
async fn handle(&self, req: GetUserRequest) -> Result<UserDto> {
let store = self.store.read().await;
store
.get(&req.id)
.cloned()
.ok_or_else(|| Error::NotFound(format!("User {} not found", req.id)))
}
`Error::NotFound` 自动映射为 HTTP 404,响应体 `{"error":"...","status":404}`。
设计要点
| 实践 | 说明 | |------|------| | Request 放 contracts | 路由元数据与 DTO 定义集中,便于查阅 API 契约 | | Handler 放 handlers | 纯业务逻辑,通过 DI 获取依赖 | | 路径参数同名字段 | `{id}` 自动绑定到 `GetUserRequest.id` | | Body 字段 Deserialize | POST/PUT 的 Request 需 `#[derive(Deserialize)]` |
小结
CRUD 是检验框架能力的试金石。rust-webx 用统一的四步模式覆盖全部 HTTP 方法,无需为每种方法学习不同 API。
下一节:[运行、调试与验证](run-and-debug.md)