Skip to main content

alun_db/
hook.rs

1//! 数据库 Hook:生命周期拦截(before/after 拦截 CRUD)
2//!
3//! 应用场景:
4//! - 数据审计:自动记录谁在什么时间修改了什么
5//! - 字段自动填充:created_at/updated_at
6//! - 软删除:deleted_at 标记
7use crate::Row;
8use crate::DbResult;
9use async_trait::async_trait;
10
11/// CRUD 生命周期 Hook
12///
13/// 所有方法默认空实现,只需覆写关心的生命周期阶段。
14///
15/// # 示例
16///
17/// ```ignore
18/// struct AuditHook;
19///
20/// #[async_trait]
21/// impl Hook for AuditHook {
22///     async fn before_insert(&self, row: &mut Row) -> DbResult<()> {
23///         row.set("created_by", "system");
24///         Ok(())
25///     }
26/// }
27/// ```
28#[async_trait]
29pub trait Hook: Send + Sync {
30    /// INSERT 之前(可修改即将插入的 Row)
31    async fn before_insert(&self, row: &mut Row) -> DbResult<()> { let _ = row; Ok(()) }
32
33    /// INSERT 之后(可读取已插入的 Row,不可修改)
34    async fn after_insert(&self, row: &Row) -> DbResult<()> { let _ = row; Ok(()) }
35
36    /// UPDATE 之前(可修改即将更新的 Row)
37    async fn before_update(&self, row: &mut Row) -> DbResult<()> { let _ = row; Ok(()) }
38
39    /// UPDATE 之后
40    async fn after_update(&self, row: &Row) -> DbResult<()> { let _ = row; Ok(()) }
41
42    /// DELETE 之前(可校验或拒绝删除)
43    async fn before_delete(&self, table: &str, id: &str) -> DbResult<()> {
44        let _ = (table, id); Ok(())
45    }
46
47    /// DELETE 之后
48    async fn after_delete(&self, table: &str, id: &str) -> DbResult<()> {
49        let _ = (table, id); Ok(())
50    }
51}
52
53/// 空 Hook —— 所有生命周期方法均为空操作
54///
55/// 用作未配置 Hook 时的默认实现,避免 `Option<Hook>` 的额外分支。
56pub struct NullHook;
57
58#[async_trait]
59impl Hook for NullHook {}
60
61/// Hook 链 —— 多个 Hook 顺序执行
62///
63/// 将多个 Hook 聚合成一个,按注册顺序依次调用。
64/// 若任一 Hook 返回 `Err`,后续 Hook 不再执行。
65pub struct HookChain {
66    /// Hook 列表
67    hooks: Vec<Box<dyn Hook>>,
68}
69
70impl HookChain {
71    /// 创建空的 Hook 链
72    pub fn new() -> Self {
73        Self { hooks: Vec::new() }
74    }
75
76    /// 添加 Hook(链式调用),返回 `&mut Self` 以支持连续添加
77    pub fn add<H: Hook + 'static>(&mut self, hook: H) -> &mut Self {
78        self.hooks.push(Box::new(hook));
79        self
80    }
81}
82
83#[async_trait]
84impl Hook for HookChain {
85    async fn before_insert(&self, row: &mut Row) -> DbResult<()> {
86        for hook in &self.hooks {
87            hook.before_insert(row).await?;
88        }
89        Ok(())
90    }
91
92    async fn after_insert(&self, row: &Row) -> DbResult<()> {
93        for hook in &self.hooks {
94            hook.after_insert(row).await?;
95        }
96        Ok(())
97    }
98
99    async fn before_update(&self, row: &mut Row) -> DbResult<()> {
100        for hook in &self.hooks {
101            hook.before_update(row).await?;
102        }
103        Ok(())
104    }
105
106    async fn after_update(&self, row: &Row) -> DbResult<()> {
107        for hook in &self.hooks {
108            hook.after_update(row).await?;
109        }
110        Ok(())
111    }
112
113    async fn before_delete(&self, table: &str, id: &str) -> DbResult<()> {
114        for hook in &self.hooks {
115            hook.before_delete(table, id).await?;
116        }
117        Ok(())
118    }
119
120    async fn after_delete(&self, table: &str, id: &str) -> DbResult<()> {
121        for hook in &self.hooks {
122            hook.after_delete(table, id).await?;
123        }
124        Ok(())
125    }
126}
127
128impl Default for HookChain {
129    fn default() -> Self {
130        Self::new()
131    }
132}
133
134/// 自动填充时间戳的 Hook —— INSERT 时设置 `created_at` 和 `updated_at`
135///
136/// UPDATE 时自动刷新 `updated_at` 字段。
137/// 时间格式使用 RFC3339(UTC),依赖 `chrono` crate。
138pub struct TimestampHook;
139
140#[async_trait]
141impl Hook for TimestampHook {
142    async fn before_insert(&self, row: &mut Row) -> DbResult<()> {
143        let now = chrono::Utc::now().to_rfc3339();
144        row.set("created_at", now.as_str());
145        row.set("updated_at", now.as_str());
146        Ok(())
147    }
148
149    async fn before_update(&self, row: &mut Row) -> DbResult<()> {
150        let now = chrono::Utc::now().to_rfc3339();
151        row.set("updated_at", now.as_str());
152        Ok(())
153    }
154}