Skip to main content

sz_orm_core/
hooks.rs

1//! 钩子系统(Hooks)— 软删除 + 多租户
2//!
3//! 对应设计文档 3.8 节「钩子系统」。
4//!
5//! # 核心概念
6//!
7//! - **HookContext**:钩子执行上下文,包含当前租户、操作人、时间戳等
8//! - **Hookable**:可钩选的 Model,支持 before/after insert/update/delete 6 个生命周期
9//! - **SoftDelete**:软删除 trait,标记删除而非物理删除
10//! - **GlobalScope**:全局查询作用域,自动过滤(如自动排除软删除行、自动追加租户条件)
11//! - **TenantScope**:多租户全局作用域,自动追加 `tenant_id = ?` 条件
12//!
13//! # 使用示例
14//!
15//! ```no_run
16//! use sz_orm_core::hooks::{HookContext, Hookable, SoftDelete, TenantScope, GlobalScope};
17//!
18//! // 1. 定义带软删除+多租户的 Model
19//! // 2. 查询时自动过滤 deleted_at IS NULL AND tenant_id = ?
20//! // 3. 删除时自动 UPDATE SET deleted_at = NOW() 而非 DELETE
21//! ```
22
23use crate::error::DbError;
24use std::collections::HashMap;
25use std::sync::{Arc, RwLock};
26
27// =====================================================================
28// HookContext — 钩子执行上下文
29// =====================================================================
30
31/// 钩子执行上下文
32///
33/// 携带请求级别的元数据,供钩子读取/修改。
34#[derive(Debug, Clone, Default)]
35pub struct HookContext {
36    /// 当前租户 ID(多租户场景)
37    pub tenant_id: Option<i64>,
38    /// 当前操作人 ID
39    pub operator_id: Option<i64>,
40    /// 时间戳(Unix 微秒)
41    pub timestamp: u64,
42    /// 额外元数据
43    pub metadata: HashMap<String, String>,
44}
45
46impl HookContext {
47    /// 创建新的空上下文
48    pub fn new() -> Self {
49        Self::default()
50    }
51
52    /// 设置租户 ID
53    pub fn with_tenant(mut self, tenant_id: i64) -> Self {
54        self.tenant_id = Some(tenant_id);
55        self
56    }
57
58    /// 设置操作人 ID
59    pub fn with_operator(mut self, operator_id: i64) -> Self {
60        self.operator_id = Some(operator_id);
61        self
62    }
63
64    /// 设置时间戳
65    pub fn with_timestamp(mut self, ts: u64) -> Self {
66        self.timestamp = ts;
67        self
68    }
69
70    /// 插入元数据
71    pub fn set_meta(&mut self, key: impl Into<String>, value: impl Into<String>) {
72        self.metadata.insert(key.into(), value.into());
73    }
74
75    /// 读取元数据
76    pub fn get_meta(&self, key: &str) -> Option<&String> {
77        self.metadata.get(key)
78    }
79}
80
81// =====================================================================
82// HookEvent — 钩子事件类型
83// =====================================================================
84
85/// 钩子事件类型
86///
87/// 在 think-orm 风格的 6 个细粒度 insert/update/delete 事件基础上,
88/// 进一步提供 6 个通用写入事件(write/save/restore):
89/// - `BeforeWrite` / `AfterWrite`:任何写入操作(insert/update)前后触发
90/// - `BeforeSave` / `AfterSave`:插入或更新保存前后触发(与 write 等价,命名借用 Rails/ActiveRecord)
91/// - `BeforeRestore` / `AfterRestore`:软删除恢复前后触发
92///
93/// 此外还提供 4 个查询/验证事件:
94/// - `BeforeFind` / `AfterFind`:单行 SELECT 前后触发(可用于查询缓存、审计)
95/// - `BeforeValidate` / `AfterValidate`:数据验证前后触发(写入前的业务规则校验)
96///
97/// 触发顺序示例(执行 INSERT 时):
98/// `BeforeWrite` → `BeforeSave` → `BeforeValidate` → `BeforeInsert` → (INSERT) → `AfterInsert` → `AfterSave` → `AfterWrite`
99///
100/// 触发顺序示例(执行 SELECT 时):
101/// `BeforeFind` → (SELECT) → `AfterFind`
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
103pub enum HookEvent {
104    /// 插入前触发
105    BeforeInsert,
106    /// 插入后触发
107    AfterInsert,
108    /// 更新前触发
109    BeforeUpdate,
110    /// 更新后触发
111    AfterUpdate,
112    /// 删除前触发
113    BeforeDelete,
114    /// 删除后触发
115    AfterDelete,
116    /// 通用写入前:insert 或 update 前均触发
117    BeforeWrite,
118    /// 通用写入后:insert 或 update 后均触发
119    AfterWrite,
120    /// 保存前(与 BeforeWrite 等价,命名风格不同)
121    BeforeSave,
122    /// 保存后(与 AfterWrite 等价,命名风格不同)
123    AfterSave,
124    /// 软删除恢复前
125    BeforeRestore,
126    /// 软删除恢复后
127    AfterRestore,
128    /// 查询前(单行 SELECT 前触发,可用于查询缓存预热、审计日志)
129    BeforeFind,
130    /// 查询后(单行 SELECT 后触发,可用于查询结果后处理、缓存填充)
131    AfterFind,
132    /// 数据验证前(写入前的业务规则校验,在 before_insert/before_update 之前触发)
133    BeforeValidate,
134    /// 数据验证后(验证完成后触发,可清理临时状态)
135    AfterValidate,
136}
137
138impl HookEvent {
139    /// 是否为 before 事件
140    pub fn is_before(&self) -> bool {
141        matches!(
142            self,
143            HookEvent::BeforeInsert
144                | HookEvent::BeforeUpdate
145                | HookEvent::BeforeDelete
146                | HookEvent::BeforeWrite
147                | HookEvent::BeforeSave
148                | HookEvent::BeforeRestore
149                | HookEvent::BeforeFind
150                | HookEvent::BeforeValidate
151        )
152    }
153
154    /// 是否为 after 事件
155    pub fn is_after(&self) -> bool {
156        matches!(
157            self,
158            HookEvent::AfterInsert
159                | HookEvent::AfterUpdate
160                | HookEvent::AfterDelete
161                | HookEvent::AfterWrite
162                | HookEvent::AfterSave
163                | HookEvent::AfterRestore
164                | HookEvent::AfterFind
165                | HookEvent::AfterValidate
166        )
167    }
168
169    /// 是否为通用写入事件(write/save)
170    pub fn is_write_level(&self) -> bool {
171        matches!(
172            self,
173            HookEvent::BeforeWrite
174                | HookEvent::AfterWrite
175                | HookEvent::BeforeSave
176                | HookEvent::AfterSave
177        )
178    }
179
180    /// 是否为查询事件(find)
181    pub fn is_find_level(&self) -> bool {
182        matches!(self, HookEvent::BeforeFind | HookEvent::AfterFind)
183    }
184
185    /// 是否为验证事件(validate)
186    pub fn is_validate_level(&self) -> bool {
187        matches!(self, HookEvent::BeforeValidate | HookEvent::AfterValidate)
188    }
189
190    /// 是否为细粒度事件(v0.2.0+ 新增的事件)
191    pub fn is_fine_grained(&self) -> bool {
192        self.is_write_level()
193            || self.is_find_level()
194            || self.is_validate_level()
195            || matches!(self, HookEvent::BeforeRestore | HookEvent::AfterRestore)
196    }
197}
198
199// =====================================================================
200// HookResult — 钩子执行结果
201// =====================================================================
202
203/// 钩子执行结果
204pub type HookResult<T> = Result<T, DbError>;
205
206// =====================================================================
207// Hookable — 可钩选 Model trait
208// =====================================================================
209
210/// 可钩选 Model trait
211///
212/// 实现 `Hookable` 的 Model 可以在 insert/update/delete 前后执行自定义逻辑。
213/// 默认实现为 no-op,Model 按需 override。
214///
215/// # 细粒度事件(v0.2.0+)
216///
217/// 在原 6 个 insert/update/delete 钩子之外,新增 6 个通用钩子:
218/// - `before_write` / `after_write`:任何写入(insert 或 update)前后均触发
219/// - `before_save` / `after_save`:保存前后触发(与 write 等价,命名风格不同)
220/// - `before_restore` / `after_restore`:软删除恢复前后触发
221///
222/// 调用方应在执行 INSERT 前依次调用 `before_write` → `before_save` → `before_insert`,
223/// INSERT 完成后依次调用 `after_insert` → `after_save` → `after_write`。
224pub trait Hookable: crate::model::Model {
225    /// 插入前钩子(默认 no-op)
226    fn before_insert(_ctx: &mut HookContext) -> HookResult<()> {
227        Ok(())
228    }
229
230    /// 插入后钩子(默认 no-op)
231    fn after_insert(_ctx: &HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
232        Ok(())
233    }
234
235    /// 更新前钩子(默认 no-op)
236    fn before_update(_ctx: &mut HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
237        Ok(())
238    }
239
240    /// 更新后钩子(默认 no-op)
241    fn after_update(_ctx: &HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
242        Ok(())
243    }
244
245    /// 删除前钩子(默认 no-op)
246    fn before_delete(_ctx: &mut HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
247        Ok(())
248    }
249
250    /// 删除后钩子(默认 no-op)
251    fn after_delete(_ctx: &HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
252        Ok(())
253    }
254
255    /// 通用写入前钩子:insert 或 update 前均触发(默认 no-op)
256    ///
257    /// 适合用于审计日志、统一字段填充(如 updated_at = now())等场景。
258    fn before_write(_ctx: &mut HookContext) -> HookResult<()> {
259        Ok(())
260    }
261
262    /// 通用写入后钩子:insert 或 update 后均触发(默认 no-op)
263    fn after_write(_ctx: &HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
264        Ok(())
265    }
266
267    /// 保存前钩子(与 `before_write` 等价,命名风格不同,默认 no-op)
268    fn before_save(_ctx: &mut HookContext) -> HookResult<()> {
269        Ok(())
270    }
271
272    /// 保存后钩子(与 `after_write` 等价,命名风格不同,默认 no-op)
273    fn after_save(_ctx: &HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
274        Ok(())
275    }
276
277    /// 软删除恢复前钩子(默认 no-op)
278    ///
279    /// 当软删除行被恢复(`UPDATE deleted_at = NULL`)时触发。
280    fn before_restore(_ctx: &mut HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
281        Ok(())
282    }
283
284    /// 软删除恢复后钩子(默认 no-op)
285    fn after_restore(_ctx: &HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
286        Ok(())
287    }
288
289    /// 单行查询前钩子(默认 no-op)
290    ///
291    /// 在执行 `SELECT * FROM ... WHERE pk = ?` 前触发。
292    /// 适合用于查询缓存预热、查询审计日志、强制查询条件注入等。
293    fn before_find(_ctx: &mut HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
294        Ok(())
295    }
296
297    /// 单行查询后钩子(默认 no-op)
298    ///
299    /// 在 `SELECT * FROM ... WHERE pk = ?` 返回结果后触发。
300    /// 适合用于查询结果缓存填充、行级权限校验等。
301    fn after_find(_ctx: &HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
302        Ok(())
303    }
304
305    /// 数据验证前钩子(默认 no-op)
306    ///
307    /// 在写入前的业务规则校验之前触发,调用顺序:
308    /// `before_write` → `before_save` → `before_validate` → `validate` → `after_validate` → `before_insert`
309    ///
310    /// 适合用于字段非空校验、字段格式校验、跨字段一致性校验等。
311    /// 失败时返回 `Err(DbError::Validation(...))`,会短路后续 before_insert。
312    fn before_validate(_ctx: &mut HookContext) -> HookResult<()> {
313        Ok(())
314    }
315
316    /// 数据验证逻辑(默认 no-op)
317    ///
318    /// 在 `before_validate` 之后、`after_validate` 之前调用。
319    /// Model 可按需 override 此方法以实现实际的业务规则校验。
320    /// 失败时返回 `Err(DbError::Validation(...))`,会短路后续 `after_validate` 与 `before_insert`。
321    fn validate(_ctx: &mut HookContext) -> HookResult<()> {
322        Ok(())
323    }
324
325    /// 数据验证后钩子(默认 no-op)
326    ///
327    /// 验证成功后触发,可用于清理临时状态、记录验证日志。
328    fn after_validate(_ctx: &HookContext) -> HookResult<()> {
329        Ok(())
330    }
331}
332
333/// 钩子执行辅助工具
334///
335/// 封装常见的钩子触发顺序,避免业务代码手动逐个调用。
336pub struct HookDispatcher;
337
338impl HookDispatcher {
339    /// INSERT 操作的完整钩子序列:
340    /// `before_write` → `before_save` → `before_validate` → `validate` → `after_validate`
341    /// → `before_insert` → (执行) → `after_insert` → `after_save` → `after_write`
342    ///
343    /// `f` 为执行实际 INSERT 操作的闭包,返回插入后的主键。
344    pub fn insert<M, F>(ctx: &mut HookContext, f: F) -> HookResult<M::PrimaryKey>
345    where
346        M: Hookable,
347        F: FnOnce(&mut HookContext) -> HookResult<M::PrimaryKey>,
348    {
349        M::before_write(ctx)?;
350        M::before_save(ctx)?;
351        M::before_validate(ctx)?;
352        M::validate(ctx)?;
353        M::after_validate(ctx)?;
354        M::before_insert(ctx)?;
355        let id = f(ctx)?;
356        M::after_insert(ctx, &id)?;
357        M::after_save(ctx, &id)?;
358        M::after_write(ctx, &id)?;
359        Ok(id)
360    }
361
362    /// UPDATE 操作的完整钩子序列(同 INSERT,含 validate)
363    pub fn update<M, F>(ctx: &mut HookContext, id: &M::PrimaryKey, f: F) -> HookResult<()>
364    where
365        M: Hookable,
366        F: FnOnce(&mut HookContext) -> HookResult<()>,
367    {
368        M::before_write(ctx)?;
369        M::before_save(ctx)?;
370        M::before_validate(ctx)?;
371        M::validate(ctx)?;
372        M::after_validate(ctx)?;
373        M::before_update(ctx, id)?;
374        f(ctx)?;
375        M::after_update(ctx, id)?;
376        M::after_save(ctx, id)?;
377        M::after_write(ctx, id)?;
378        Ok(())
379    }
380
381    /// DELETE 操作的完整钩子序列
382    pub fn delete<M, F>(ctx: &mut HookContext, id: &M::PrimaryKey, f: F) -> HookResult<()>
383    where
384        M: Hookable,
385        F: FnOnce(&mut HookContext) -> HookResult<()>,
386    {
387        M::before_delete(ctx, id)?;
388        f(ctx)?;
389        M::after_delete(ctx, id)?;
390        Ok(())
391    }
392
393    /// RESTORE 操作(软删除恢复)的完整钩子序列
394    pub fn restore<M, F>(ctx: &mut HookContext, id: &M::PrimaryKey, f: F) -> HookResult<()>
395    where
396        M: Hookable,
397        F: FnOnce(&mut HookContext) -> HookResult<()>,
398    {
399        M::before_restore(ctx, id)?;
400        f(ctx)?;
401        M::after_restore(ctx, id)?;
402        Ok(())
403    }
404
405    /// FIND 操作(单行查询)的完整钩子序列:
406    /// `before_find` → (执行 SELECT) → `after_find`
407    ///
408    /// `f` 为执行实际 SELECT 操作的闭包,返回查询到的实例。
409    pub fn find<M, F>(ctx: &mut HookContext, id: &M::PrimaryKey, f: F) -> HookResult<()>
410    where
411        M: Hookable,
412        F: FnOnce(&mut HookContext) -> HookResult<()>,
413    {
414        M::before_find(ctx, id)?;
415        f(ctx)?;
416        M::after_find(ctx, id)?;
417        Ok(())
418    }
419
420    /// 仅触发验证钩子序列(不执行实际写入):
421    /// `before_validate` → `validate` → `after_validate`
422    ///
423    /// 适用于调用方需独立校验数据、不立刻写入的场景。
424    pub fn validate<M>(ctx: &mut HookContext) -> HookResult<()>
425    where
426        M: Hookable,
427    {
428        M::before_validate(ctx)?;
429        M::validate(ctx)?;
430        M::after_validate(ctx)?;
431        Ok(())
432    }
433}
434
435// =====================================================================
436// SoftDelete — 软删除 trait
437// =====================================================================
438
439/// 软删除 trait
440///
441/// 实现此 trait 的 Model 在调用 delete 时,实际执行
442/// `UPDATE SET {field} = NOW() WHERE pk = ?` 而非 `DELETE`。
443pub trait SoftDelete: crate::model::Model {
444    /// 软删除字段名(如 `deleted_at`)
445    fn soft_delete_field() -> &'static str;
446
447    /// 是否已软删除(由 Model 数据行判断)
448    fn is_deleted(&self) -> bool;
449}
450
451// =====================================================================
452// GlobalScope — 全局查询作用域
453// =====================================================================
454
455/// 全局查询作用域
456///
457/// 应用到所有查询的隐式过滤条件。
458/// 典型实现:软删除作用域(`deleted_at IS NULL`)、多租户作用域(`tenant_id = ?`)。
459///
460/// 注:此 trait 不要求实现 `Model`,因为作用域本身只是一个标记类型,
461/// 真正的 Model 由泛型参数 `M` 携带。
462pub trait GlobalScope {
463    /// 作用域名称(用于调试)
464    fn scope_name() -> &'static str;
465
466    /// 返回需要追加的 WHERE 条件 SQL 片段
467    ///
468    /// 返回 `None` 表示无附加条件。
469    /// 返回 `Some((sql, params))` 表示追加 `AND {sql}`,绑定 `params`。
470    fn apply_scope(ctx: &HookContext) -> Option<(String, Vec<crate::value::Value>)>;
471}
472
473// =====================================================================
474// SoftDeleteScope — 软删除全局作用域
475// =====================================================================
476
477/// 软删除全局作用域
478///
479/// 自动追加 `AND {soft_delete_field} IS NULL` 到所有查询。
480/// 需配合 `SoftDelete` trait 使用。
481pub struct SoftDeleteScope;
482
483impl<M: SoftDelete> GlobalScope for (SoftDeleteScope, M) {
484    fn scope_name() -> &'static str {
485        "soft_delete"
486    }
487
488    fn apply_scope(_ctx: &HookContext) -> Option<(String, Vec<crate::value::Value>)> {
489        // 使用完全限定语法避免与 Model::soft_delete_field 歧义
490        let field = <M as SoftDelete>::soft_delete_field();
491        Some((format!("{} IS NULL", field), vec![]))
492    }
493}
494
495// =====================================================================
496// TenantScope — 多租户全局作用域
497// =====================================================================
498
499/// 多租户全局作用域
500///
501/// 自动追加 `AND tenant_id = ?` 到所有查询,绑定 `ctx.tenant_id`。
502/// 若 `ctx.tenant_id` 为 None,则不追加条件(允许跨租户查询,需调用方自行保证安全)。
503pub struct TenantScope;
504
505/// 多租户 Model trait
506///
507/// 实现此 trait 的 Model 自动获得 `TenantScope` 全局作用域。
508pub trait TenantModel: crate::model::Model {
509    /// 租户字段名(默认 `tenant_id`)
510    fn tenant_field() -> &'static str {
511        "tenant_id"
512    }
513
514    /// 获取当前行的租户 ID
515    fn tenant_id(&self) -> i64;
516
517    /// 设置租户 ID
518    fn set_tenant_id(&mut self, tenant_id: i64);
519}
520
521impl<M: TenantModel> GlobalScope for (TenantScope, M) {
522    fn scope_name() -> &'static str {
523        "tenant"
524    }
525
526    fn apply_scope(ctx: &HookContext) -> Option<(String, Vec<crate::value::Value>)> {
527        ctx.tenant_id.map(|tid| {
528            (
529                format!("{} = ?", <M as TenantModel>::tenant_field()),
530                vec![crate::value::Value::I64(tid)],
531            )
532        })
533    }
534}
535
536// =====================================================================
537// HookRegistry — 钩子注册表(运行时钩子)
538// =====================================================================
539
540/// 运行时钩子函数类型
541pub type HookFn = Arc<dyn Fn(&HookContext) -> HookResult<()> + Send + Sync>;
542
543/// 钩子注册表
544///
545/// 支持运行时注册全局钩子函数,按事件类型分组。
546/// 与 `Hookable` trait 互补:trait 用于编译期已知钩子,注册表用于运行时插件。
547pub struct HookRegistry {
548    hooks: RwLock<HashMap<HookEvent, Vec<HookFn>>>,
549}
550
551impl Default for HookRegistry {
552    fn default() -> Self {
553        Self::new()
554    }
555}
556
557impl HookRegistry {
558    /// 创建空注册表
559    pub fn new() -> Self {
560        Self {
561            hooks: RwLock::new(HashMap::new()),
562        }
563    }
564
565    /// 注册钩子
566    pub fn register(&self, event: HookEvent, hook: HookFn) {
567        // lock poisoned 时降级为 no-op,避免级联 panic。
568        if let Ok(mut hooks) = self.hooks.write() {
569            hooks.entry(event).or_default().push(hook);
570        }
571    }
572
573    /// 执行某事件的所有钩子
574    ///
575    /// 按注册顺序执行,任一钩子失败则立即返回错误。
576    pub fn dispatch(&self, event: HookEvent, ctx: &HookContext) -> HookResult<()> {
577        let hooks = match self.hooks.read() {
578            Ok(h) => h,
579            Err(_) => return Ok(()), // lock poisoned → no-op
580        };
581        if let Some(fns) = hooks.get(&event) {
582            for f in fns {
583                f(ctx)?;
584            }
585        }
586        Ok(())
587    }
588
589    /// 清除某事件的所有钩子
590    pub fn clear(&self, event: HookEvent) {
591        if let Ok(mut hooks) = self.hooks.write() {
592            hooks.remove(&event);
593        }
594    }
595
596    /// 清除所有钩子
597    pub fn clear_all(&self) {
598        if let Ok(mut hooks) = self.hooks.write() {
599            hooks.clear();
600        }
601    }
602
603    /// 获取某事件的钩子数量
604    pub fn count(&self, event: HookEvent) -> usize {
605        self.hooks
606            .read()
607            .map(|h| h.get(&event).map(|v| v.len()).unwrap_or(0))
608            .unwrap_or(0)
609    }
610}
611
612// =====================================================================
613// ScopeRegistry — 全局作用域注册表
614// =====================================================================
615
616/// 全局作用域注册表
617///
618/// 管理多个 GlobalScope 的启用/禁用状态。
619/// 典型用法:临时禁用软删除作用域以查询已删除行(`without_scope`)。
620pub struct ScopeRegistry {
621    disabled: RwLock<Vec<String>>,
622}
623
624impl Default for ScopeRegistry {
625    fn default() -> Self {
626        Self::new()
627    }
628}
629
630impl ScopeRegistry {
631    /// 创建空注册表(所有作用域默认启用)
632    pub fn new() -> Self {
633        Self {
634            disabled: RwLock::new(Vec::new()),
635        }
636    }
637
638    /// 禁用指定作用域
639    pub fn disable(&self, scope_name: impl Into<String>) {
640        if let Ok(mut disabled) = self.disabled.write() {
641            let name = scope_name.into();
642            if !disabled.contains(&name) {
643                disabled.push(name);
644            }
645        }
646    }
647
648    /// 启用指定作用域
649    pub fn enable(&self, scope_name: &str) {
650        if let Ok(mut disabled) = self.disabled.write() {
651            disabled.retain(|n| n != scope_name);
652        }
653    }
654
655    /// 检查作用域是否启用
656    pub fn is_enabled(&self, scope_name: &str) -> bool {
657        self.disabled
658            .read()
659            .map(|d| !d.iter().any(|n| n == scope_name))
660            .unwrap_or(true)
661    }
662
663    /// 在闭包内临时禁用作用域
664    ///
665    /// ```no_run
666    /// # use sz_orm_core::hooks::ScopeRegistry;
667    /// let registry = ScopeRegistry::new();
668    /// registry.without_scope("soft_delete", || {
669    ///     // 此处查询会包含已软删除的行
670    /// });
671    /// ```
672    pub fn without_scope<F, R>(&self, scope_name: &str, f: F) -> R
673    where
674        F: FnOnce() -> R,
675    {
676        self.disable(scope_name);
677        let result = f();
678        self.enable(scope_name);
679        result
680    }
681}
682
683// =====================================================================
684// v7.0.0 composable-plugin:ExtensionPointRegistry 扩展点注册表
685// =====================================================================
686
687#[cfg(feature = "composable-plugin")]
688mod extension_point {
689    use std::collections::HashMap;
690    use std::sync::Arc;
691
692    use parking_lot::RwLock;
693
694    use super::{HookContext, HookResult};
695
696    /// 扩展点类型
697    ///
698    /// 定义插件可挂载的生命周期扩展点。
699    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
700    pub enum ExtensionPoint {
701        /// 连接建立前
702        BeforeConnect,
703        /// SQL 生成后
704        AfterSqlGen,
705        /// 结果映射前
706        BeforeResultMap,
707        /// 插入前
708        BeforeInsert,
709        /// 更新前
710        BeforeUpdate,
711        /// 删除前
712        BeforeDelete,
713        /// 插入后
714        AfterInsert,
715        /// 更新后
716        AfterUpdate,
717        /// 删除后
718        AfterDelete,
719    }
720
721    /// 扩展点处理器 trait
722    pub trait ExtensionHandler: Send + Sync {
723        /// 处理器名称
724        fn name(&self) -> &str;
725
726        /// 处理扩展点事件
727        fn handle(&self, ctx: &mut HookContext) -> HookResult<()>;
728    }
729
730    /// 扩展点注册表
731    ///
732    /// 按扩展点分组注册处理器,触发时按注册顺序执行。
733    pub struct ExtensionPointRegistry {
734        handlers: RwLock<HashMap<ExtensionPoint, Vec<Arc<dyn ExtensionHandler>>>>,
735    }
736
737    impl Default for ExtensionPointRegistry {
738        fn default() -> Self {
739            Self::new()
740        }
741    }
742
743    impl ExtensionPointRegistry {
744        /// 创建空注册表
745        pub fn new() -> Self {
746            Self {
747                handlers: RwLock::new(HashMap::new()),
748            }
749        }
750
751        /// 注册扩展点处理器
752        pub fn register(&self, point: ExtensionPoint, handler: Arc<dyn ExtensionHandler>) {
753            self.handlers
754                .write()
755                .entry(point)
756                .or_default()
757                .push(handler);
758        }
759
760        /// 触发扩展点的所有处理器(按注册顺序)
761        pub fn trigger(&self, point: ExtensionPoint, ctx: &mut HookContext) -> HookResult<()> {
762            let handlers = self.handlers.read();
763            if let Some(fns) = handlers.get(&point) {
764                let fns = fns.clone();
765                drop(handlers);
766                for f in &fns {
767                    f.handle(ctx)?;
768                }
769            }
770            Ok(())
771        }
772
773        /// 获取某扩展点的处理器数量
774        pub fn count(&self, point: ExtensionPoint) -> usize {
775            self.handlers
776                .read()
777                .get(&point)
778                .map(|v| v.len())
779                .unwrap_or(0)
780        }
781
782        /// 清除某扩展点的所有处理器
783        pub fn clear(&self, point: ExtensionPoint) {
784            self.handlers.write().remove(&point);
785        }
786    }
787
788    #[cfg(test)]
789    mod tests {
790        use super::*;
791        use std::sync::atomic::{AtomicU32, Ordering};
792
793        struct CounterHandler {
794            name: String,
795            counter: Arc<AtomicU32>,
796        }
797
798        impl ExtensionHandler for CounterHandler {
799            fn name(&self) -> &str {
800                &self.name
801            }
802            fn handle(&self, _ctx: &mut HookContext) -> HookResult<()> {
803                self.counter.fetch_add(1, Ordering::SeqCst);
804                Ok(())
805            }
806        }
807
808        #[test]
809        fn extension_point_register_and_trigger() {
810            let reg = ExtensionPointRegistry::new();
811            let counter = Arc::new(AtomicU32::new(0));
812
813            reg.register(
814                ExtensionPoint::BeforeInsert,
815                Arc::new(CounterHandler {
816                    name: "h1".into(),
817                    counter: counter.clone(),
818                }),
819            );
820
821            assert_eq!(reg.count(ExtensionPoint::BeforeInsert), 1);
822            let mut ctx = HookContext::new();
823            reg.trigger(ExtensionPoint::BeforeInsert, &mut ctx).unwrap();
824            assert_eq!(counter.load(Ordering::SeqCst), 1);
825        }
826
827        #[test]
828        fn extension_point_multiple_handlers_ordered() {
829            let reg = ExtensionPointRegistry::new();
830            let c1 = Arc::new(AtomicU32::new(0));
831            let c2 = Arc::new(AtomicU32::new(0));
832
833            reg.register(
834                ExtensionPoint::AfterUpdate,
835                Arc::new(CounterHandler {
836                    name: "first".into(),
837                    counter: c1.clone(),
838                }),
839            );
840            reg.register(
841                ExtensionPoint::AfterUpdate,
842                Arc::new(CounterHandler {
843                    name: "second".into(),
844                    counter: c2.clone(),
845                }),
846            );
847
848            let mut ctx = HookContext::new();
849            reg.trigger(ExtensionPoint::AfterUpdate, &mut ctx).unwrap();
850            assert_eq!(c1.load(Ordering::SeqCst), 1);
851            assert_eq!(c2.load(Ordering::SeqCst), 1);
852        }
853
854        #[test]
855        fn extension_point_unregistered_returns_ok() {
856            let reg = ExtensionPointRegistry::new();
857            let mut ctx = HookContext::new();
858            assert!(reg.trigger(ExtensionPoint::BeforeConnect, &mut ctx).is_ok());
859        }
860
861        #[test]
862        fn extension_point_clear() {
863            let reg = ExtensionPointRegistry::new();
864            let counter = Arc::new(AtomicU32::new(0));
865            reg.register(
866                ExtensionPoint::BeforeDelete,
867                Arc::new(CounterHandler {
868                    name: "h".into(),
869                    counter,
870                }),
871            );
872            assert_eq!(reg.count(ExtensionPoint::BeforeDelete), 1);
873            reg.clear(ExtensionPoint::BeforeDelete);
874            assert_eq!(reg.count(ExtensionPoint::BeforeDelete), 0);
875        }
876    }
877}
878
879#[cfg(feature = "composable-plugin")]
880pub use extension_point::{ExtensionHandler, ExtensionPoint, ExtensionPointRegistry};
881
882// =====================================================================
883// 测试
884// =====================================================================
885
886#[cfg(test)]
887mod tests {
888    use super::*;
889
890    #[test]
891    fn hook_context_builder() {
892        let ctx = HookContext::new()
893            .with_tenant(42)
894            .with_operator(1)
895            .with_timestamp(1700000000);
896
897        assert_eq!(ctx.tenant_id, Some(42));
898        assert_eq!(ctx.operator_id, Some(1));
899        assert_eq!(ctx.timestamp, 1700000000);
900    }
901
902    #[test]
903    fn hook_context_metadata() {
904        let mut ctx = HookContext::new();
905        ctx.set_meta("source", "api");
906        ctx.set_meta("ip", "127.0.0.1");
907
908        assert_eq!(ctx.get_meta("source"), Some(&"api".to_string()));
909        assert_eq!(ctx.get_meta("ip"), Some(&"127.0.0.1".to_string()));
910        assert_eq!(ctx.get_meta("missing"), None);
911    }
912
913    #[test]
914    fn hook_event_is_before_after() {
915        assert!(HookEvent::BeforeInsert.is_before());
916        assert!(!HookEvent::BeforeInsert.is_after());
917        assert!(HookEvent::AfterInsert.is_after());
918        assert!(!HookEvent::AfterInsert.is_before());
919    }
920
921    #[test]
922    fn hook_registry_register_and_dispatch() {
923        let registry = HookRegistry::new();
924        let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
925
926        let c = Arc::clone(&counter);
927        registry.register(
928            HookEvent::BeforeInsert,
929            Arc::new(move |_ctx| {
930                c.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
931                Ok(())
932            }),
933        );
934
935        let ctx = HookContext::new();
936        registry.dispatch(HookEvent::BeforeInsert, &ctx).unwrap();
937        registry.dispatch(HookEvent::BeforeInsert, &ctx).unwrap();
938
939        assert_eq!(counter.load(std::sync::atomic::Ordering::SeqCst), 2);
940    }
941
942    #[test]
943    fn hook_registry_dispatch_no_hooks() {
944        let registry = HookRegistry::new();
945        let ctx = HookContext::new();
946        // 无钩子时 dispatch 应返回 Ok
947        assert!(registry.dispatch(HookEvent::BeforeInsert, &ctx).is_ok());
948    }
949
950    #[test]
951    fn hook_registry_clear() {
952        let registry = HookRegistry::new();
953        registry.register(HookEvent::BeforeInsert, Arc::new(|_ctx| Ok(())));
954        assert_eq!(registry.count(HookEvent::BeforeInsert), 1);
955
956        registry.clear(HookEvent::BeforeInsert);
957        assert_eq!(registry.count(HookEvent::BeforeInsert), 0);
958    }
959
960    #[test]
961    fn hook_registry_clear_all() {
962        let registry = HookRegistry::new();
963        registry.register(HookEvent::BeforeInsert, Arc::new(|_ctx| Ok(())));
964        registry.register(HookEvent::AfterInsert, Arc::new(|_ctx| Ok(())));
965        registry.register(HookEvent::BeforeUpdate, Arc::new(|_ctx| Ok(())));
966
967        registry.clear_all();
968        assert_eq!(registry.count(HookEvent::BeforeInsert), 0);
969        assert_eq!(registry.count(HookEvent::AfterInsert), 0);
970        assert_eq!(registry.count(HookEvent::BeforeUpdate), 0);
971    }
972
973    #[test]
974    fn scope_registry_enable_disable() {
975        let registry = ScopeRegistry::new();
976
977        assert!(registry.is_enabled("soft_delete"));
978        assert!(registry.is_enabled("tenant"));
979
980        registry.disable("soft_delete");
981        assert!(!registry.is_enabled("soft_delete"));
982        assert!(registry.is_enabled("tenant"));
983
984        registry.enable("soft_delete");
985        assert!(registry.is_enabled("soft_delete"));
986    }
987
988    #[test]
989    fn scope_registry_without_scope() {
990        let registry = ScopeRegistry::new();
991        assert!(registry.is_enabled("soft_delete"));
992
993        let result = registry.without_scope("soft_delete", || {
994            assert!(!registry.is_enabled("soft_delete"));
995            42
996        });
997
998        assert_eq!(result, 42);
999        assert!(registry.is_enabled("soft_delete"));
1000    }
1001
1002    #[test]
1003    fn hook_registry_short_circuit_on_error() {
1004        let registry = HookRegistry::new();
1005        let called = Arc::new(std::sync::atomic::AtomicU32::new(0));
1006
1007        let c1 = Arc::clone(&called);
1008        registry.register(
1009            HookEvent::BeforeInsert,
1010            Arc::new(move |_ctx| {
1011                c1.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1012                Ok(())
1013            }),
1014        );
1015
1016        registry.register(
1017            HookEvent::BeforeInsert,
1018            Arc::new(|_ctx| Err(DbError::Hook("second hook failed".into()))),
1019        );
1020
1021        let c3 = Arc::clone(&called);
1022        registry.register(
1023            HookEvent::BeforeInsert,
1024            Arc::new(move |_ctx| {
1025                c3.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1026                Ok(())
1027            }),
1028        );
1029
1030        let ctx = HookContext::new();
1031        let result = registry.dispatch(HookEvent::BeforeInsert, &ctx);
1032
1033        assert!(result.is_err());
1034        // 第一个钩子执行,第二个返回错误,第三个不应执行
1035        assert_eq!(called.load(std::sync::atomic::Ordering::SeqCst), 1);
1036    }
1037
1038    // ===== 细粒度钩子(v0.2.0+)测试 =====
1039
1040    #[test]
1041    fn hook_event_is_write_level() {
1042        assert!(HookEvent::BeforeWrite.is_write_level());
1043        assert!(HookEvent::AfterWrite.is_write_level());
1044        assert!(HookEvent::BeforeSave.is_write_level());
1045        assert!(HookEvent::AfterSave.is_write_level());
1046        assert!(!HookEvent::BeforeInsert.is_write_level());
1047        assert!(!HookEvent::AfterDelete.is_write_level());
1048        assert!(!HookEvent::BeforeRestore.is_write_level());
1049    }
1050
1051    #[test]
1052    fn hook_event_before_after_covers_new_variants() {
1053        assert!(HookEvent::BeforeWrite.is_before());
1054        assert!(HookEvent::BeforeSave.is_before());
1055        assert!(HookEvent::BeforeRestore.is_before());
1056        assert!(HookEvent::AfterWrite.is_after());
1057        assert!(HookEvent::AfterSave.is_after());
1058        assert!(HookEvent::AfterRestore.is_after());
1059        assert!(!HookEvent::AfterWrite.is_before());
1060        assert!(!HookEvent::BeforeWrite.is_after());
1061    }
1062
1063    #[test]
1064    fn hook_registry_supports_new_events() {
1065        let registry = HookRegistry::new();
1066        let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
1067
1068        for event in [
1069            HookEvent::BeforeWrite,
1070            HookEvent::AfterWrite,
1071            HookEvent::BeforeSave,
1072            HookEvent::AfterSave,
1073            HookEvent::BeforeRestore,
1074            HookEvent::AfterRestore,
1075        ] {
1076            let c = Arc::clone(&counter);
1077            registry.register(
1078                event,
1079                Arc::new(move |_ctx| {
1080                    c.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1081                    Ok(())
1082                }),
1083            );
1084        }
1085
1086        let ctx = HookContext::new();
1087        for event in [
1088            HookEvent::BeforeWrite,
1089            HookEvent::AfterWrite,
1090            HookEvent::BeforeSave,
1091            HookEvent::AfterSave,
1092            HookEvent::BeforeRestore,
1093            HookEvent::AfterRestore,
1094        ] {
1095            registry.dispatch(event, &ctx).unwrap();
1096        }
1097
1098        assert_eq!(
1099            counter.load(std::sync::atomic::Ordering::SeqCst),
1100            6,
1101            "所有细粒度事件均应被正确注册与触发"
1102        );
1103    }
1104
1105    // ===== HookDispatcher 测试 =====
1106
1107    struct DispatchTestModel;
1108    impl crate::model::Model for DispatchTestModel {
1109        type PrimaryKey = i64;
1110        fn table_name() -> &'static str {
1111            "dispatch_test"
1112        }
1113        fn pk(&self) -> Self::PrimaryKey {
1114            0
1115        }
1116        fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
1117    }
1118
1119    // 全局计数器:用于在 after_* 钩子中记录调用次数(&HookContext 不可变)
1120    static DISPATCH_CALLS: std::sync::OnceLock<Arc<std::sync::atomic::AtomicU32>> =
1121        std::sync::OnceLock::new();
1122
1123    fn dispatch_calls() -> Arc<std::sync::atomic::AtomicU32> {
1124        DISPATCH_CALLS
1125            .get_or_init(|| Arc::new(std::sync::atomic::AtomicU32::new(0)))
1126            .clone()
1127    }
1128
1129    impl Hookable for DispatchTestModel {
1130        fn before_write(ctx: &mut HookContext) -> HookResult<()> {
1131            ctx.set_meta("before_write", "1");
1132            Ok(())
1133        }
1134        fn before_save(ctx: &mut HookContext) -> HookResult<()> {
1135            ctx.set_meta("before_save", "1");
1136            Ok(())
1137        }
1138        fn before_validate(ctx: &mut HookContext) -> HookResult<()> {
1139            ctx.set_meta("before_validate", "1");
1140            Ok(())
1141        }
1142        fn after_validate(ctx: &HookContext) -> HookResult<()> {
1143            assert_eq!(ctx.get_meta("before_validate"), Some(&"1".to_string()));
1144            ctx_set_meta_for_after(ctx, "after_validate", "1");
1145            Ok(())
1146        }
1147        fn before_insert(ctx: &mut HookContext) -> HookResult<()> {
1148            ctx.set_meta("before_insert", "1");
1149            Ok(())
1150        }
1151        fn after_insert(ctx: &HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
1152            assert_eq!(ctx.get_meta("before_write"), Some(&"1".to_string()));
1153            assert_eq!(ctx.get_meta("before_save"), Some(&"1".to_string()));
1154            assert_eq!(ctx.get_meta("before_insert"), Some(&"1".to_string()));
1155            assert_eq!(ctx.get_meta("before_validate"), Some(&"1".to_string()));
1156            Ok(())
1157        }
1158        fn after_save(_ctx: &HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
1159            dispatch_calls().fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1160            Ok(())
1161        }
1162        fn after_write(_ctx: &HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
1163            dispatch_calls().fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1164            Ok(())
1165        }
1166        fn before_find(ctx: &mut HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
1167            ctx.set_meta("before_find", "1");
1168            Ok(())
1169        }
1170        fn after_find(ctx: &HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
1171            assert_eq!(ctx.get_meta("before_find"), Some(&"1".to_string()));
1172            ctx_set_meta_for_after(ctx, "after_find", "1");
1173            Ok(())
1174        }
1175    }
1176
1177    // after_* 钩子接收 &HookContext(不可变),无法直接 set_meta
1178    // 使用 AtomicU32 计数器记录 after_* 调用次数(无锁,无线程安全问题)
1179    static AFTER_VALIDATE_COUNT: std::sync::atomic::AtomicU32 =
1180        std::sync::atomic::AtomicU32::new(0);
1181    static AFTER_FIND_COUNT: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1182
1183    fn ctx_set_meta_for_after(_ctx: &HookContext, key: &str, _value: &str) {
1184        match key {
1185            "after_validate" => {
1186                AFTER_VALIDATE_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1187            }
1188            "after_find" => {
1189                AFTER_FIND_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1190            }
1191            _ => {}
1192        }
1193    }
1194
1195    fn after_call_was(key: &str) -> bool {
1196        match key {
1197            "after_validate" => AFTER_VALIDATE_COUNT.load(std::sync::atomic::Ordering::SeqCst) > 0,
1198            "after_find" => AFTER_FIND_COUNT.load(std::sync::atomic::Ordering::SeqCst) > 0,
1199            _ => false,
1200        }
1201    }
1202
1203    fn reset_after_calls() {
1204        AFTER_VALIDATE_COUNT.store(0, std::sync::atomic::Ordering::SeqCst);
1205        AFTER_FIND_COUNT.store(0, std::sync::atomic::Ordering::SeqCst);
1206    }
1207
1208    // 串行化锁:全局静态计数器是共享的,并行测试会互相干扰
1209    static HOOK_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
1210
1211    #[test]
1212    fn hook_dispatcher_insert_full_sequence() {
1213        let _guard = HOOK_TEST_LOCK.lock().unwrap();
1214        dispatch_calls().store(0, std::sync::atomic::Ordering::SeqCst);
1215        reset_after_calls();
1216        let mut ctx = HookContext::new();
1217        let id = HookDispatcher::insert::<DispatchTestModel, _>(&mut ctx, |_ctx| Ok(42_i64));
1218        assert!(id.is_ok());
1219        assert_eq!(id.unwrap(), 42);
1220        // 验证 before 钩子都已执行
1221        assert_eq!(ctx.get_meta("before_write"), Some(&"1".to_string()));
1222        assert_eq!(ctx.get_meta("before_save"), Some(&"1".to_string()));
1223        assert_eq!(ctx.get_meta("before_insert"), Some(&"1".to_string()));
1224        // 验证 before_validate + after_validate 都已执行
1225        assert_eq!(ctx.get_meta("before_validate"), Some(&"1".to_string()));
1226        assert!(after_call_was("after_validate"));
1227        // 验证 after_save + after_write 都已执行
1228        assert_eq!(
1229            dispatch_calls().load(std::sync::atomic::Ordering::SeqCst),
1230            2
1231        );
1232    }
1233
1234    #[test]
1235    fn hook_dispatcher_insert_short_circuit_on_before_write_error() {
1236        struct ErrorModel;
1237        impl crate::model::Model for ErrorModel {
1238            type PrimaryKey = i64;
1239            fn table_name() -> &'static str {
1240                "error_model"
1241            }
1242            fn pk(&self) -> Self::PrimaryKey {
1243                0
1244            }
1245            fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
1246        }
1247        impl Hookable for ErrorModel {
1248            fn before_write(_ctx: &mut HookContext) -> HookResult<()> {
1249                Err(DbError::Hook("before_write failed".into()))
1250            }
1251        }
1252
1253        let mut ctx = HookContext::new();
1254        let result = HookDispatcher::insert::<ErrorModel, _>(&mut ctx, |_ctx| Ok(1_i64));
1255        assert!(result.is_err());
1256        // before_write 失败,不应执行实际操作
1257    }
1258
1259    #[test]
1260    fn hook_dispatcher_insert_short_circuit_on_before_validate_error() {
1261        struct ValidationFailModel;
1262        impl crate::model::Model for ValidationFailModel {
1263            type PrimaryKey = i64;
1264            fn table_name() -> &'static str {
1265                "validation_fail"
1266            }
1267            fn pk(&self) -> Self::PrimaryKey {
1268                0
1269            }
1270            fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
1271        }
1272        impl Hookable for ValidationFailModel {
1273            fn before_validate(_ctx: &mut HookContext) -> HookResult<()> {
1274                Err(DbError::Validation("name is required".into()))
1275            }
1276        }
1277
1278        let mut ctx = HookContext::new();
1279        let called = Arc::new(std::sync::atomic::AtomicU32::new(0));
1280        let c = Arc::clone(&called);
1281        let result = HookDispatcher::insert::<ValidationFailModel, _>(&mut ctx, move |_ctx| {
1282            c.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1283            Ok(1_i64)
1284        });
1285        assert!(result.is_err());
1286        // before_validate 失败,实际 INSERT 不应执行
1287        assert_eq!(
1288            called.load(std::sync::atomic::Ordering::SeqCst),
1289            0,
1290            "before_validate 失败应短路 INSERT 操作"
1291        );
1292        // 错误类型应是 Validation
1293        match result.unwrap_err() {
1294            DbError::Validation(msg) => assert_eq!(msg, "name is required"),
1295            other => panic!("期望 Validation 错误,得到 {:?}", other),
1296        }
1297    }
1298
1299    #[test]
1300    fn hook_dispatcher_update_full_sequence() {
1301        let _guard = HOOK_TEST_LOCK.lock().unwrap();
1302        dispatch_calls().store(0, std::sync::atomic::Ordering::SeqCst);
1303        reset_after_calls();
1304        let mut ctx = HookContext::new();
1305        let result =
1306            HookDispatcher::update::<DispatchTestModel, _>(&mut ctx, &42_i64, |_ctx| Ok(()));
1307        assert!(result.is_ok());
1308        // update 也会触发 after_save + after_write
1309        assert_eq!(
1310            dispatch_calls().load(std::sync::atomic::Ordering::SeqCst),
1311            2
1312        );
1313        // update 也会触发 validate
1314        assert_eq!(ctx.get_meta("before_validate"), Some(&"1".to_string()));
1315        assert!(after_call_was("after_validate"));
1316    }
1317
1318    #[test]
1319    fn hook_dispatcher_delete_full_sequence() {
1320        let mut ctx = HookContext::new();
1321        let result =
1322            HookDispatcher::delete::<DispatchTestModel, _>(&mut ctx, &42_i64, |_ctx| Ok(()));
1323        assert!(result.is_ok());
1324    }
1325
1326    #[test]
1327    fn hook_dispatcher_restore_full_sequence() {
1328        let mut ctx = HookContext::new();
1329        let result =
1330            HookDispatcher::restore::<DispatchTestModel, _>(&mut ctx, &42_i64, |_ctx| Ok(()));
1331        assert!(result.is_ok());
1332    }
1333
1334    #[test]
1335    fn hook_dispatcher_find_full_sequence() {
1336        let _guard = HOOK_TEST_LOCK.lock().unwrap();
1337        dispatch_calls().store(0, std::sync::atomic::Ordering::SeqCst);
1338        reset_after_calls();
1339        let mut ctx = HookContext::new();
1340        let called = Arc::new(std::sync::atomic::AtomicU32::new(0));
1341        let c = Arc::clone(&called);
1342        let result = HookDispatcher::find::<DispatchTestModel, _>(&mut ctx, &42_i64, move |_ctx| {
1343            c.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1344            Ok(())
1345        });
1346        assert!(result.is_ok());
1347        assert_eq!(
1348            called.load(std::sync::atomic::Ordering::SeqCst),
1349            1,
1350            "SELECT 操作应执行一次"
1351        );
1352        // 验证 before_find + after_find 都已执行
1353        assert_eq!(ctx.get_meta("before_find"), Some(&"1".to_string()));
1354        assert!(after_call_was("after_find"));
1355    }
1356
1357    #[test]
1358    fn hook_dispatcher_find_short_circuit_on_before_find_error() {
1359        struct FindFailModel;
1360        impl crate::model::Model for FindFailModel {
1361            type PrimaryKey = i64;
1362            fn table_name() -> &'static str {
1363                "find_fail"
1364            }
1365            fn pk(&self) -> Self::PrimaryKey {
1366                0
1367            }
1368            fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
1369        }
1370        impl Hookable for FindFailModel {
1371            fn before_find(_ctx: &mut HookContext, _id: &Self::PrimaryKey) -> HookResult<()> {
1372                Err(DbError::Hook("before_find blocked".into()))
1373            }
1374        }
1375
1376        let mut ctx = HookContext::new();
1377        let called = Arc::new(std::sync::atomic::AtomicU32::new(0));
1378        let c = Arc::clone(&called);
1379        let result = HookDispatcher::find::<FindFailModel, _>(&mut ctx, &1_i64, move |_ctx| {
1380            c.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1381            Ok(())
1382        });
1383        assert!(result.is_err());
1384        assert_eq!(
1385            called.load(std::sync::atomic::Ordering::SeqCst),
1386            0,
1387            "before_find 失败应短路 SELECT"
1388        );
1389    }
1390
1391    #[test]
1392    fn hook_dispatcher_validate_standalone() {
1393        reset_after_calls();
1394        let mut ctx = HookContext::new();
1395        let result = HookDispatcher::validate::<DispatchTestModel>(&mut ctx);
1396        assert!(result.is_ok());
1397        assert_eq!(ctx.get_meta("before_validate"), Some(&"1".to_string()));
1398        assert!(after_call_was("after_validate"));
1399    }
1400
1401    #[test]
1402    fn hook_event_is_find_level_and_is_validate_level() {
1403        assert!(HookEvent::BeforeFind.is_find_level());
1404        assert!(HookEvent::AfterFind.is_find_level());
1405        assert!(HookEvent::BeforeValidate.is_validate_level());
1406        assert!(HookEvent::AfterValidate.is_validate_level());
1407        assert!(!HookEvent::BeforeInsert.is_find_level());
1408        assert!(!HookEvent::BeforeInsert.is_validate_level());
1409        assert!(!HookEvent::BeforeWrite.is_find_level());
1410        assert!(!HookEvent::BeforeWrite.is_validate_level());
1411    }
1412
1413    #[test]
1414    fn hook_event_is_fine_grained_covers_all_v02_events() {
1415        // v0.2.0+ 新增的事件均应被识别为细粒度
1416        assert!(HookEvent::BeforeWrite.is_fine_grained());
1417        assert!(HookEvent::AfterWrite.is_fine_grained());
1418        assert!(HookEvent::BeforeSave.is_fine_grained());
1419        assert!(HookEvent::AfterSave.is_fine_grained());
1420        assert!(HookEvent::BeforeRestore.is_fine_grained());
1421        assert!(HookEvent::AfterRestore.is_fine_grained());
1422        assert!(HookEvent::BeforeFind.is_fine_grained());
1423        assert!(HookEvent::AfterFind.is_fine_grained());
1424        assert!(HookEvent::BeforeValidate.is_fine_grained());
1425        assert!(HookEvent::AfterValidate.is_fine_grained());
1426        // 原生 6 事件不应标记为细粒度
1427        assert!(!HookEvent::BeforeInsert.is_fine_grained());
1428        assert!(!HookEvent::AfterInsert.is_fine_grained());
1429        assert!(!HookEvent::BeforeUpdate.is_fine_grained());
1430        assert!(!HookEvent::AfterUpdate.is_fine_grained());
1431        assert!(!HookEvent::BeforeDelete.is_fine_grained());
1432        assert!(!HookEvent::AfterDelete.is_fine_grained());
1433    }
1434
1435    #[test]
1436    fn hook_registry_supports_find_and_validate_events() {
1437        let registry = HookRegistry::new();
1438        let counter = Arc::new(std::sync::atomic::AtomicU32::new(0));
1439
1440        for event in [
1441            HookEvent::BeforeFind,
1442            HookEvent::AfterFind,
1443            HookEvent::BeforeValidate,
1444            HookEvent::AfterValidate,
1445        ] {
1446            let c = Arc::clone(&counter);
1447            registry.register(
1448                event,
1449                Arc::new(move |_ctx| {
1450                    c.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
1451                    Ok(())
1452                }),
1453            );
1454        }
1455
1456        let ctx = HookContext::new();
1457        for event in [
1458            HookEvent::BeforeFind,
1459            HookEvent::AfterFind,
1460            HookEvent::BeforeValidate,
1461            HookEvent::AfterValidate,
1462        ] {
1463            registry.dispatch(event, &ctx).unwrap();
1464        }
1465
1466        assert_eq!(
1467            counter.load(std::sync::atomic::Ordering::SeqCst),
1468            4,
1469            "find/validate 钩子应能被注册与触发"
1470        );
1471    }
1472
1473    #[test]
1474    fn db_error_validation_error_code_and_display() {
1475        let err = DbError::Validation("name required".into());
1476        assert_eq!(err.error_code(), "DB021");
1477        assert_eq!(format!("{}", err), "Validation error: name required");
1478    }
1479}