Skip to main content

anycms_event/
trigger.rs

1//! 触发规则引擎模块,提供基于事件模式的动态规则触发能力。
2//!
3//! 通过触发规则引擎,系统可以:
4//! - 动态配置事件到动作的映射规则
5//! - 支持事件名称模式匹配(通配符 `*` 和 `**`)
6//! - 支持条件过滤(基于 JSON payload)
7//! - 运行时添加、删除、启用/禁用规则
8//! - 与外部系统(如 WorkflowEngine)集成
9//!
10//! # 与 WorkflowEngine 集成示例
11//!
12//! ```ignore
13//! use std::sync::Arc;
14//! use anycms_event::prelude::*;
15//! use anycms_event::trigger::{TriggerRuleEngine, TriggerRule, TriggerAction, TriggerContext};
16//!
17//! // 1. 创建触发规则引擎
18//! let engine = TriggerRuleEngine::new(bus.clone());
19//!
20//! // 2. 注册自定义 Action(例如触发 workflow)
21//! engine.register_action("workflow", |ctx: TriggerContext| {
22//!     // 调用 WorkflowEngine.emit()
23//!     let workflow_engine = ctx.action_config["engine"].clone();
24//!     // ... 触发 workflow
25//!     async move { Ok(()) }
26//! });
27//!
28//! // 3. 添加规则
29//! engine.add_rule(TriggerRule {
30//!     id: "rule-1".to_string(),
31//!     name: "内容发布触发 Sitemap".to_string(),
32//!     event_pattern: "content.published".to_string(),
33//!     condition: None,
34//!     action_type: "workflow".to_string(),
35//!     action_config: serde_json::json!({"workflow_id": "generate-sitemap"}),
36//!     enabled: true,
37//!     priority: 0,
38//! });
39//!
40//! // 4. 启动引擎(订阅 EventBus)
41//! engine.start().await?;
42//! ```
43
44use std::collections::HashMap;
45use std::sync::Arc;
46use std::sync::RwLock;
47use std::sync::atomic::{AtomicBool, Ordering};
48
49use serde::{Deserialize, Serialize};
50
51use crate::bus::EventBus;
52use crate::error::Result;
53
54// ── ConditionLimits ─────────────────────────────────────────────────
55
56/// Safety limits for condition evaluation in the trigger rule engine.
57///
58/// These limits prevent DoS attacks via maliciously crafted conditions
59/// (e.g., deeply nested JSON paths, excessive operators, or huge strings).
60#[derive(Clone, Debug)]
61pub struct ConditionLimits {
62    /// Maximum path depth for `json_path_get()`.
63    ///
64    /// Paths with more segments than this are rejected.
65    /// Default: 10.
66    pub max_path_depth: usize,
67    /// Maximum number of operators per condition object.
68    ///
69    /// Conditions with more operators than this are rejected.
70    /// Default: 20.
71    pub max_operators: usize,
72    /// Maximum string length (in bytes) for `$contains` operations.
73    ///
74    /// Strings longer than this are rejected.
75    /// Default: 10_000 (10 KB).
76    pub max_string_length: usize,
77}
78
79impl Default for ConditionLimits {
80    fn default() -> Self {
81        Self {
82            max_path_depth: 10,
83            max_operators: 20,
84            max_string_length: 10_000,
85        }
86    }
87}
88
89// ── TriggerContext ────────────────────────────────────────────────
90
91/// 触发动作的上下文数据。
92///
93/// 传递给 [`TriggerAction`] 回调,包含事件的完整信息。
94#[derive(Clone, Debug, Serialize, Deserialize)]
95pub struct TriggerContext {
96    /// 匹配到的事件名称。
97    pub event_name: String,
98    /// 事件的 JSON payload 数据。
99    pub event_data: serde_json::Value,
100    /// 匹配的规则 ID。
101    pub rule_id: String,
102    /// 匹配的规则名称。
103    pub rule_name: String,
104    /// 规则的 action 配置。
105    pub action_config: serde_json::Value,
106}
107
108// ── TriggerAction trait ───────────────────────────────────────────
109
110/// 触发动作的异步回调类型。
111pub type TriggerActionFn = Arc<
112    dyn Fn(TriggerContext) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send>>
113        + Send
114        + Sync,
115>;
116
117// ── TriggerRule ───────────────────────────────────────────────────
118
119/// 触发规则定义。
120///
121/// 定义了一个事件模式到动作的映射规则。
122#[derive(Clone, Debug, Serialize, Deserialize)]
123pub struct TriggerRule {
124    /// 规则唯一 ID。
125    pub id: String,
126    /// 规则名称(用于显示)。
127    #[serde(default)]
128    pub name: String,
129    /// 事件名称模式。
130    ///
131    /// 支持通配符:
132    /// - `user.created` 精确匹配
133    /// - `user.*` 匹配单层通配
134    /// - `user.**` 匹配多层通配
135    pub event_pattern: String,
136    /// 条件过滤(JSON payload 字段匹配)。
137    ///
138    /// 例如: `{"status": {"$eq": "published"}}`
139    #[serde(default)]
140    pub condition: Option<serde_json::Value>,
141    /// 动作类型(对应已注册的 action handler)。
142    pub action_type: String,
143    /// 动作配置(传递给 action handler 的参数)。
144    #[serde(default)]
145    pub action_config: serde_json::Value,
146    /// 是否启用。
147    #[serde(default = "default_true")]
148    pub enabled: bool,
149    /// 优先级(数值越小越先执行)。
150    #[serde(default)]
151    pub priority: i32,
152}
153
154fn default_true() -> bool {
155    true
156}
157
158// ── RuleStorage trait ──────────────────────────────────────────────
159
160/// 触发规则存储后端 trait。
161///
162/// 实现此 trait 以自定义规则持久化方式(如数据库、文件等)。
163/// 默认提供 [`InMemoryRuleStorage`](内存存储)。
164///
165/// # Example
166///
167/// ```ignore
168/// use anycms_event::trigger::{RuleStorage, TriggerRule, InMemoryRuleStorage};
169///
170/// let storage = Arc::new(InMemoryRuleStorage::new());
171/// let engine = TriggerRuleEngine::with_storage(bus, storage.clone());
172/// ```
173pub trait RuleStorage: Send + Sync + 'static {
174    /// 添加一条规则。
175    fn add(&self, rule: TriggerRule);
176
177    /// 移除一条规则(按 ID)。返回被移除的规则。
178    fn remove(&self, rule_id: &str) -> Option<TriggerRule>;
179
180    /// 获取指定 ID 的规则。
181    fn get(&self, rule_id: &str) -> Option<TriggerRule>;
182
183    /// 更新一条规则(根据 rule.id 查找并替换)。返回是否成功。
184    fn update(&self, rule: TriggerRule) -> bool;
185
186    /// 获取所有规则(按 priority 排序)。
187    fn list(&self) -> Vec<TriggerRule>;
188
189    /// 获取规则数量。
190    fn count(&self) -> usize;
191}
192
193// ── InMemoryRuleStorage ────────────────────────────────────────────
194
195/// 内存规则存储。
196///
197/// 使用 `RwLock<Vec<TriggerRule>>` 存储规则,添加时按 priority 排序。
198pub struct InMemoryRuleStorage {
199    rules: RwLock<Vec<TriggerRule>>,
200}
201
202impl InMemoryRuleStorage {
203    /// 创建新的内存规则存储。
204    pub fn new() -> Self {
205        Self {
206            rules: RwLock::new(Vec::new()),
207        }
208    }
209}
210
211impl Default for InMemoryRuleStorage {
212    fn default() -> Self {
213        Self::new()
214    }
215}
216
217impl RuleStorage for InMemoryRuleStorage {
218    fn add(&self, rule: TriggerRule) {
219        let mut rules = self.rules.write().unwrap();
220        rules.push(rule);
221        rules.sort_by_key(|r| r.priority);
222    }
223
224    fn remove(&self, rule_id: &str) -> Option<TriggerRule> {
225        let mut rules = self.rules.write().unwrap();
226        let pos = rules.iter().position(|r| r.id == rule_id)?;
227        Some(rules.remove(pos))
228    }
229
230    fn get(&self, rule_id: &str) -> Option<TriggerRule> {
231        self.rules
232            .read()
233            .unwrap()
234            .iter()
235            .find(|r| r.id == rule_id)
236            .cloned()
237    }
238
239    fn update(&self, rule: TriggerRule) -> bool {
240        let mut rules = self.rules.write().unwrap();
241        if let Some(pos) = rules.iter().position(|r| r.id == rule.id) {
242            rules[pos] = rule;
243            rules.sort_by_key(|r| r.priority);
244            true
245        } else {
246            false
247        }
248    }
249
250    fn list(&self) -> Vec<TriggerRule> {
251        self.rules.read().unwrap().clone()
252    }
253
254    fn count(&self) -> usize {
255        self.rules.read().unwrap().len()
256    }
257}
258
259// ── TriggerEngineState ────────────────────────────────────────────
260
261/// Internal state of the trigger engine, shared via Arc between the engine
262/// and the publish callback spawned tasks.
263struct TriggerEngineState {
264    storage: Arc<dyn RuleStorage>,
265    actions: RwLock<HashMap<String, TriggerActionFn>>,
266    running: AtomicBool,
267    /// Safety limits for condition evaluation.
268    limits: ConditionLimits,
269}
270
271// ── TriggerRuleEngine ─────────────────────────────────────────────
272
273/// 触发规则引擎。
274///
275/// 通过 EventBus 的 publish callback 机制监听所有事件,根据配置的规则匹配事件并执行对应的动作。
276///
277/// # 生命周期
278///
279/// 1. 创建引擎(`new`)
280/// 2. 注册 action handlers(`register_action`)
281/// 3. 添加规则(`add_rule` / `add_rules`)
282/// 4. 启动引擎(`start`)- 注册 publish callback 开始监听事件
283/// 5. 运行时管理规则(`update_rule`, `remove_rule`, `enable_rule`, `disable_rule`)
284pub struct TriggerRuleEngine {
285    bus: EventBus,
286    state: Arc<TriggerEngineState>,
287    /// Safety limits for condition evaluation.
288    limits: ConditionLimits,
289}
290
291impl TriggerRuleEngine {
292    /// 创建一个新的触发规则引擎。
293    ///
294    /// 引擎创建后需要调用 [`start`](Self::start) 开始监听事件。
295    /// 默认使用 [`InMemoryRuleStorage`](内存存储)。
296    pub fn new(bus: EventBus) -> Self {
297        Self {
298            bus,
299            state: Arc::new(TriggerEngineState {
300                storage: Arc::new(InMemoryRuleStorage::new()),
301                actions: RwLock::new(HashMap::new()),
302                running: AtomicBool::new(false),
303                limits: ConditionLimits::default(),
304            }),
305            limits: ConditionLimits::default(),
306        }
307    }
308
309    /// 使用自定义存储后端创建触发规则引擎。
310    ///
311    /// 用于需要持久化规则到数据库或其他存储的场景。
312    pub fn with_storage(bus: EventBus, storage: Arc<dyn RuleStorage>) -> Self {
313        Self {
314            bus,
315            state: Arc::new(TriggerEngineState {
316                storage,
317                actions: RwLock::new(HashMap::new()),
318                running: AtomicBool::new(false),
319                limits: ConditionLimits::default(),
320            }),
321            limits: ConditionLimits::default(),
322        }
323    }
324
325    /// Create a new engine with custom condition evaluation limits.
326    pub fn with_limits(bus: EventBus, limits: ConditionLimits) -> Self {
327        Self {
328            bus,
329            state: Arc::new(TriggerEngineState {
330                storage: Arc::new(InMemoryRuleStorage::new()),
331                actions: RwLock::new(HashMap::new()),
332                running: AtomicBool::new(false),
333                limits: limits.clone(),
334            }),
335            limits,
336        }
337    }
338
339    /// Create a new engine with custom storage and condition evaluation limits.
340    pub fn with_storage_and_limits(
341        bus: EventBus,
342        storage: Arc<dyn RuleStorage>,
343        limits: ConditionLimits,
344    ) -> Self {
345        Self {
346            bus,
347            state: Arc::new(TriggerEngineState {
348                storage,
349                actions: RwLock::new(HashMap::new()),
350                running: AtomicBool::new(false),
351                limits: limits.clone(),
352            }),
353            limits,
354        }
355    }
356
357    /// 获取存储后端引用。
358    ///
359    /// 用于高级管理操作,如批量导入/导出规则。
360    pub fn storage(&self) -> &Arc<dyn RuleStorage> {
361        &self.state.storage
362    }
363
364    /// 注册一个动作处理器。
365    ///
366    /// `action_type` 对应 [`TriggerRule::action_type`] 中配置的值。
367    pub fn register_action<F, Fut>(&self, action_type: &str, handler: F)
368    where
369        F: Fn(TriggerContext) -> Fut + Send + Sync + 'static,
370        Fut: std::future::Future<Output = Result<()>> + Send + 'static,
371    {
372        let wrapped: TriggerActionFn = Arc::new(move |ctx| {
373            let fut = handler(ctx);
374            Box::pin(fut)
375                as std::pin::Pin<Box<dyn std::future::Future<Output = Result<()>> + Send>>
376        });
377        self.state
378            .actions
379            .write()
380            .unwrap()
381            .insert(action_type.to_string(), wrapped);
382    }
383
384    /// 添加一条触发规则。
385    pub fn add_rule(&self, rule: TriggerRule) {
386        self.state.storage.add(rule);
387    }
388
389    /// 批量添加触发规则。
390    pub fn add_rules(&self, new_rules: Vec<TriggerRule>) {
391        for rule in new_rules {
392            self.state.storage.add(rule);
393        }
394    }
395
396    /// 移除一条规则(按 ID)。
397    ///
398    /// 返回被移除的规则(如果存在)。
399    pub fn remove_rule(&self, rule_id: &str) -> Option<TriggerRule> {
400        self.state.storage.remove(rule_id)
401    }
402
403    /// 更新一条规则。
404    ///
405    /// 根据 `rule.id` 查找并替换。
406    pub fn update_rule(&self, rule: TriggerRule) -> bool {
407        self.state.storage.update(rule)
408    }
409
410    /// 启用一条规则。
411    pub fn enable_rule(&self, rule_id: &str) -> bool {
412        if let Some(mut rule) = self.state.storage.get(rule_id) {
413            rule.enabled = true;
414            self.state.storage.update(rule)
415        } else {
416            false
417        }
418    }
419
420    /// 禁用一条规则。
421    pub fn disable_rule(&self, rule_id: &str) -> bool {
422        if let Some(mut rule) = self.state.storage.get(rule_id) {
423            rule.enabled = false;
424            self.state.storage.update(rule)
425        } else {
426            false
427        }
428    }
429
430    /// 获取所有规则。
431    pub fn list_rules(&self) -> Vec<TriggerRule> {
432        self.state.storage.list()
433    }
434
435    /// 获取指定 ID 的规则。
436    pub fn get_rule(&self, rule_id: &str) -> Option<TriggerRule> {
437        self.state.storage.get(rule_id)
438    }
439
440    /// 获取规则数量。
441    pub fn rule_count(&self) -> usize {
442        self.state.storage.count()
443    }
444
445    /// 获取当前的条件评估限制配置。
446    pub fn limits(&self) -> &ConditionLimits {
447        &self.limits
448    }
449
450    /// 列出已注册的 action 类型。
451    pub fn list_action_types(&self) -> Vec<String> {
452        self.state.actions.read().unwrap().keys().cloned().collect()
453    }
454
455    /// 检查引擎是否正在运行。
456    pub fn is_running(&self) -> bool {
457        self.state.running.load(Ordering::Relaxed)
458    }
459
460    /// 启动触发规则引擎。
461    ///
462    /// 通过 EventBus 的 publish callback 机制监听所有已发布的事件,
463    /// 当事件的 `to_json()` 返回 JSON 数据时,自动匹配规则并执行动作。
464    /// 如果引擎已经在运行,则不做任何操作。
465    pub async fn start(&self) -> Result<()> {
466        if self.state.running.load(Ordering::Relaxed) {
467            return Ok(());
468        }
469
470        let state = self.state.clone();
471        self.bus.register_publish_callback(Arc::new(
472            move |event_name: &str, data: serde_json::Value| {
473                let state = state.clone();
474                let event_name = event_name.to_string();
475                tokio::spawn(async move {
476                    // Only process if engine is still running
477                    if !state.running.load(Ordering::Relaxed) {
478                        return;
479                    }
480                    let _ =
481                        TriggerRuleEngine::evaluate_rules(&state, &event_name, &data).await;
482                });
483            },
484        ));
485
486        self.state.running.store(true, Ordering::Relaxed);
487        Ok(())
488    }
489
490    /// 停止触发规则引擎。
491    ///
492    /// Sets the running flag to false. Note that already-spawned tasks from
493    /// publish callbacks may still complete — this is acceptable for now.
494    pub fn stop(&self) {
495        self.state.running.store(false, Ordering::Relaxed);
496    }
497
498    /// 处理一个事件,匹配规则并执行动作。
499    ///
500    /// 此方法通常在引擎内部自动调用(通过 publish callback),
501    /// 也可以手动调用用于测试或自定义集成。
502    pub async fn process_event(
503        &self,
504        event_name: &str,
505        event_data: &serde_json::Value,
506    ) -> Vec<Result<()>> {
507        Self::evaluate_rules(&self.state, event_name, event_data).await
508    }
509
510    /// Internal method that evaluates rules against an event using shared state.
511    ///
512    /// This is called both by [`Self::process_event`] and by the publish callback.
513    async fn evaluate_rules(
514        state: &Arc<TriggerEngineState>,
515        event_name: &str,
516        event_data: &serde_json::Value,
517    ) -> Vec<Result<()>> {
518        // Clone both rules and actions to drop the RwLock guards before awaiting.
519        // This is required for `Send` safety when called from `tokio::spawn`.
520        let rules = state.storage.list();
521        let actions = state.actions.read().unwrap().clone();
522
523        let mut results = Vec::new();
524
525        for rule in &rules {
526            if !rule.enabled {
527                continue;
528            }
529
530            // 检查事件名称是否匹配模式
531            if !crate::topic::matches(&rule.event_pattern, event_name) {
532                continue;
533            }
534
535            // 检查条件过滤
536            if let Some(ref condition) = rule.condition {
537                if !matches_condition(event_data, condition, &state.limits) {
538                    continue;
539                }
540            }
541
542            // 查找 action handler
543            let Some(action_fn) = actions.get(&rule.action_type) else {
544                tracing::warn!(
545                    rule_id = %rule.id,
546                    action_type = %rule.action_type,
547                    "Trigger rule references unknown action type"
548                );
549                continue;
550            };
551
552            // 构建上下文并执行
553            let ctx = TriggerContext {
554                event_name: event_name.to_string(),
555                event_data: event_data.clone(),
556                rule_id: rule.id.clone(),
557                rule_name: rule.name.clone(),
558                action_config: rule.action_config.clone(),
559            };
560
561            let result = action_fn(ctx).await;
562            results.push(result);
563        }
564
565        results
566    }
567}
568
569impl Drop for TriggerRuleEngine {
570    fn drop(&mut self) {
571        self.stop();
572    }
573}
574
575// ── TriggerEvent ──────────────────────────────────────────────────
576
577/// 触发规则引擎使用的内部事件类型。
578///
579/// 封装了事件名称和 JSON payload,通过 EventBus 传递。
580#[derive(Clone, Debug)]
581pub struct TriggerEvent {
582    /// 事件名称。
583    pub name: String,
584    /// 事件的 JSON payload 数据。
585    pub data: serde_json::Value,
586}
587
588impl crate::event::Event for TriggerEvent {
589    fn event_name() -> &'static str {
590        "trigger.event"
591    }
592
593    fn topic() -> &'static str {
594        "trigger"
595    }
596}
597
598// ── 条件匹配 ──────────────────────────────────────────────────────
599
600/// 简单的 JSON 条件匹配。
601///
602/// 支持的操作符:
603/// - `$eq` 等于
604/// - `$ne` 不等于
605/// - `$gt` 大于
606/// - `$gte` 大于等于
607/// - `$lt` 小于
608/// - `$lte` 小于等于
609/// - `$in` 包含在列表中
610/// - `$contains` 字符串包含
611fn matches_condition(
612    data: &serde_json::Value,
613    condition: &serde_json::Value,
614    limits: &ConditionLimits,
615) -> bool {
616    let Some(condition_obj) = condition.as_object() else {
617        return true;
618    };
619
620    let mut operator_count = 0;
621
622    for (field, ops) in condition_obj {
623        let value = json_path_get(data, field, limits.max_path_depth);
624        let Some(value) = value else {
625            return false;
626        };
627
628        if !match_operators(
629            value,
630            ops,
631            &mut operator_count,
632            limits.max_operators,
633            limits.max_string_length,
634        ) {
635            return false;
636        }
637    }
638
639    true
640}
641
642/// 通过点分路径获取 JSON 值。
643fn json_path_get<'a>(
644    data: &'a serde_json::Value,
645    path: &str,
646    max_depth: usize,
647) -> Option<&'a serde_json::Value> {
648    let mut current = data;
649    let mut depth = 0;
650
651    for segment in path.split('.') {
652        if depth >= max_depth {
653            tracing::warn!(
654                path = %path,
655                depth = depth,
656                max = max_depth,
657                "json_path_get exceeded maximum depth, rejecting"
658            );
659            return None;
660        }
661        current = current.get(segment)?;
662        depth += 1;
663    }
664
665    Some(current)
666}
667
668/// 对一个值执行操作符匹配。
669fn match_operators(
670    value: &serde_json::Value,
671    ops: &serde_json::Value,
672    operator_count: &mut usize,
673    max_operators: usize,
674    max_string_length: usize,
675) -> bool {
676    let Some(ops_obj) = ops.as_object() else {
677        // 如果 ops 不是对象,则作为精确匹配
678        return value == ops;
679    };
680
681    for (op, expected) in ops_obj {
682        *operator_count += 1;
683
684        if *operator_count > max_operators {
685            tracing::warn!(
686                count = *operator_count,
687                max = max_operators,
688                "Condition exceeded maximum operator count, rejecting"
689            );
690            return false;
691        }
692
693        match op.as_str() {
694            "$eq" => {
695                if value != expected {
696                    return false;
697                }
698            }
699            "$ne" => {
700                if value == expected {
701                    return false;
702                }
703            }
704            "$gt" => {
705                if !json_value_gt(value, expected) {
706                    return false;
707                }
708            }
709            "$gte" => {
710                if !json_value_gte(value, expected) {
711                    return false;
712                }
713            }
714            "$lt" => {
715                if !json_value_lt(value, expected) {
716                    return false;
717                }
718            }
719            "$lte" => {
720                if !json_value_lte(value, expected) {
721                    return false;
722                }
723            }
724            "$in" => {
725                let Some(arr) = expected.as_array() else {
726                    return false;
727                };
728                if !arr.contains(value) {
729                    return false;
730                }
731            }
732            "$contains" => {
733                let (Some(s), Some(pattern)) = (value.as_str(), expected.as_str()) else {
734                    return false;
735                };
736                if s.len() > max_string_length || pattern.len() > max_string_length {
737                    tracing::warn!(
738                        s_len = s.len(),
739                        p_len = pattern.len(),
740                        max = max_string_length,
741                        "$contains string exceeded length limit, rejecting"
742                    );
743                    return false;
744                }
745                if !s.contains(pattern) {
746                    return false;
747                }
748            }
749            _ => {
750                tracing::warn!(operator = %op, "Unknown condition operator, ignoring");
751            }
752        }
753    }
754
755    true
756}
757
758fn json_value_gt(a: &serde_json::Value, b: &serde_json::Value) -> bool {
759    match (a, b) {
760        (serde_json::Value::Number(a), serde_json::Value::Number(b)) => {
761            if let (Some(a), Some(b)) = (a.as_f64(), b.as_f64()) {
762                return a > b;
763            }
764            false
765        }
766        (serde_json::Value::String(a), serde_json::Value::String(b)) => a > b,
767        _ => false,
768    }
769}
770
771fn json_value_gte(a: &serde_json::Value, b: &serde_json::Value) -> bool {
772    a == b || json_value_gt(a, b)
773}
774
775fn json_value_lt(a: &serde_json::Value, b: &serde_json::Value) -> bool {
776    match (a, b) {
777        (serde_json::Value::Number(a), serde_json::Value::Number(b)) => {
778            if let (Some(a), Some(b)) = (a.as_f64(), b.as_f64()) {
779                return a < b;
780            }
781            false
782        }
783        (serde_json::Value::String(a), serde_json::Value::String(b)) => a < b,
784        _ => false,
785    }
786}
787
788fn json_value_lte(a: &serde_json::Value, b: &serde_json::Value) -> bool {
789    a == b || json_value_lt(a, b)
790}
791
792#[cfg(test)]
793mod tests {
794    use super::*;
795
796    fn make_rule(
797        id: &str,
798        pattern: &str,
799        action_type: &str,
800        action_config: serde_json::Value,
801    ) -> TriggerRule {
802        TriggerRule {
803            id: id.to_string(),
804            name: format!("Rule {}", id),
805            event_pattern: pattern.to_string(),
806            condition: None,
807            action_type: action_type.to_string(),
808            action_config,
809            enabled: true,
810            priority: 0,
811        }
812    }
813
814    // ── Rule CRUD tests ──────────────────────────────────────
815
816    #[test]
817    fn test_add_and_list_rules() {
818        let bus = EventBus::new();
819        let engine = TriggerRuleEngine::new(bus);
820
821        engine.add_rule(make_rule(
822            "r1",
823            "user.*",
824            "log",
825            serde_json::json!({}),
826        ));
827        engine.add_rule(make_rule(
828            "r2",
829            "order.**",
830            "notify",
831            serde_json::json!({}),
832        ));
833
834        assert_eq!(engine.rule_count(), 2);
835        let rules = engine.list_rules();
836        assert_eq!(rules.len(), 2);
837    }
838
839    #[test]
840    fn test_remove_rule() {
841        let bus = EventBus::new();
842        let engine = TriggerRuleEngine::new(bus);
843
844        engine.add_rule(make_rule("r1", "user.*", "log", serde_json::json!({})));
845        engine.add_rule(make_rule("r2", "order.*", "log", serde_json::json!({})));
846
847        let removed = engine.remove_rule("r1").unwrap();
848        assert_eq!(removed.id, "r1");
849        assert_eq!(engine.rule_count(), 1);
850        assert!(engine.remove_rule("nonexistent").is_none());
851    }
852
853    #[test]
854    fn test_update_rule() {
855        let bus = EventBus::new();
856        let engine = TriggerRuleEngine::new(bus);
857
858        engine.add_rule(make_rule("r1", "user.*", "log", serde_json::json!({})));
859
860        let mut updated = make_rule("r1", "user.**", "notify", serde_json::json!({}));
861        updated.name = "Updated Rule".to_string();
862        assert!(engine.update_rule(updated));
863
864        let rule = engine.get_rule("r1").unwrap();
865        assert_eq!(rule.event_pattern, "user.**");
866        assert_eq!(rule.action_type, "notify");
867        assert_eq!(rule.name, "Updated Rule");
868    }
869
870    #[test]
871    fn test_enable_disable_rule() {
872        let bus = EventBus::new();
873        let engine = TriggerRuleEngine::new(bus);
874
875        engine.add_rule(make_rule("r1", "user.*", "log", serde_json::json!({})));
876
877        assert!(engine.disable_rule("r1"));
878        assert!(!engine.get_rule("r1").unwrap().enabled);
879
880        assert!(engine.enable_rule("r1"));
881        assert!(engine.get_rule("r1").unwrap().enabled);
882
883        assert!(!engine.disable_rule("nonexistent"));
884    }
885
886    #[test]
887    fn test_priority_sorting() {
888        let bus = EventBus::new();
889        let engine = TriggerRuleEngine::new(bus);
890
891        let mut r1 = make_rule("r1", "a", "log", serde_json::json!({}));
892        r1.priority = 10;
893        let mut r2 = make_rule("r2", "b", "log", serde_json::json!({}));
894        r2.priority = 1;
895        let mut r3 = make_rule("r3", "c", "log", serde_json::json!({}));
896        r3.priority = 5;
897
898        engine.add_rules(vec![r1, r2, r3]);
899
900        let rules = engine.list_rules();
901        assert_eq!(rules[0].id, "r2"); // priority 1
902        assert_eq!(rules[1].id, "r3"); // priority 5
903        assert_eq!(rules[2].id, "r1"); // priority 10
904    }
905
906    #[test]
907    fn test_register_action() {
908        let bus = EventBus::new();
909        let engine = TriggerRuleEngine::new(bus);
910
911        engine.register_action("log", |_ctx: TriggerContext| async { Ok(()) });
912        engine.register_action("notify", |_ctx: TriggerContext| async { Ok(()) });
913
914        let types = engine.list_action_types();
915        assert_eq!(types.len(), 2);
916        assert!(types.contains(&"log".to_string()));
917    }
918
919    // ── Condition matching tests ─────────────────────────────
920
921    #[test]
922    fn test_condition_eq() {
923        let data = serde_json::json!({"status": "published", "level": 3});
924        let condition = serde_json::json!({"status": {"$eq": "published"}});
925        assert!(matches_condition(&data, &condition, &ConditionLimits::default()));
926
927        let condition = serde_json::json!({"status": {"$eq": "draft"}});
928        assert!(!matches_condition(&data, &condition, &ConditionLimits::default()));
929    }
930
931    #[test]
932    fn test_condition_ne() {
933        let data = serde_json::json!({"status": "published"});
934        let condition = serde_json::json!({"status": {"$ne": "draft"}});
935        assert!(matches_condition(&data, &condition, &ConditionLimits::default()));
936    }
937
938    #[test]
939    fn test_condition_gt_lt() {
940        let data = serde_json::json!({"amount": 500});
941        let condition = serde_json::json!({"amount": {"$gt": 100, "$lt": 1000}});
942        assert!(matches_condition(&data, &condition, &ConditionLimits::default()));
943
944        let data = serde_json::json!({"amount": 50});
945        assert!(!matches_condition(&data, &condition, &ConditionLimits::default()));
946    }
947
948    #[test]
949    fn test_condition_in() {
950        let data = serde_json::json!({"category": "books"});
951        let condition = serde_json::json!({"category": {"$in": ["books", "electronics"]}});
952        assert!(matches_condition(&data, &condition, &ConditionLimits::default()));
953
954        let data = serde_json::json!({"category": "clothing"});
955        assert!(!matches_condition(&data, &condition, &ConditionLimits::default()));
956    }
957
958    #[test]
959    fn test_condition_contains() {
960        let data = serde_json::json!({"title": "Hello World Article"});
961        let condition = serde_json::json!({"title": {"$contains": "World"}});
962        assert!(matches_condition(&data, &condition, &ConditionLimits::default()));
963
964        let condition = serde_json::json!({"title": {"$contains": "Missing"}});
965        assert!(!matches_condition(&data, &condition, &ConditionLimits::default()));
966    }
967
968    #[test]
969    fn test_condition_nested_path() {
970        let data = serde_json::json!({"user": {"level": 5}});
971        let condition = serde_json::json!({"user.level": {"$gte": 3}});
972        assert!(matches_condition(&data, &condition, &ConditionLimits::default()));
973    }
974
975    #[test]
976    fn test_condition_missing_field() {
977        let data = serde_json::json!({"status": "ok"});
978        let condition = serde_json::json!({"missing_field": {"$eq": "value"}});
979        assert!(!matches_condition(&data, &condition, &ConditionLimits::default()));
980    }
981
982    #[test]
983    fn test_condition_no_condition() {
984        let data = serde_json::json!({"status": "ok"});
985        assert!(matches_condition(&data, &serde_json::Value::Null, &ConditionLimits::default()));
986    }
987
988    #[test]
989    fn test_condition_limits_path_depth() {
990        let data = serde_json::json!({"a": {"b": {"c": {"d": {"e": "deep"}}}}});
991
992        // Path with 5 segments should work with default limits (max_depth=10)
993        let limits = ConditionLimits::default();
994        assert!(matches_condition(
995            &data,
996            &serde_json::json!({"a.b.c.d.e": {"$eq": "deep"}}),
997            &limits
998        ));
999
1000        // Path with max_depth=2 should reject a 5-segment path
1001        let strict_limits = ConditionLimits {
1002            max_path_depth: 2,
1003            ..Default::default()
1004        };
1005        assert!(!matches_condition(
1006            &data,
1007            &serde_json::json!({"a.b.c.d.e": {"$eq": "deep"}}),
1008            &strict_limits
1009        ));
1010    }
1011
1012    #[test]
1013    fn test_condition_limits_operator_count() {
1014        let data = serde_json::json!({"value": 42});
1015
1016        // 3 operators should work with default limits (max_operators=20)
1017        let limits = ConditionLimits::default();
1018        assert!(matches_condition(
1019            &data,
1020            &serde_json::json!({"value": {"$gt": 0, "$lt": 100, "$ne": 50}}),
1021            &limits
1022        ));
1023
1024        // 3 operators should fail with max_operators=2
1025        let strict_limits = ConditionLimits {
1026            max_operators: 2,
1027            ..Default::default()
1028        };
1029        assert!(!matches_condition(
1030            &data,
1031            &serde_json::json!({"value": {"$gt": 0, "$lt": 100, "$ne": 50}}),
1032            &strict_limits
1033        ));
1034    }
1035
1036    #[test]
1037    fn test_condition_limits_string_length() {
1038        let long_string = "a".repeat(20_000);
1039        let data = serde_json::json!({"text": long_string});
1040
1041        // Default limits should reject strings > 10,000 chars
1042        let limits = ConditionLimits::default();
1043        assert!(!matches_condition(
1044            &data,
1045            &serde_json::json!({"text": {"$contains": "a"}}),
1046            &limits
1047        ));
1048
1049        // Relaxed limits should allow it
1050        let relaxed_limits = ConditionLimits {
1051            max_string_length: 100_000,
1052            ..Default::default()
1053        };
1054        assert!(matches_condition(
1055            &data,
1056            &serde_json::json!({"text": {"$contains": "a"}}),
1057            &relaxed_limits
1058        ));
1059    }
1060
1061    // ── process_event tests ──────────────────────────────────
1062
1063    #[tokio::test]
1064    async fn test_process_event_basic_matching() {
1065        let bus = EventBus::new();
1066        let engine = TriggerRuleEngine::new(bus);
1067
1068        let executed: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(Vec::new()));
1069        let executed_clone = executed.clone();
1070
1071        engine.register_action("collect", move |ctx: TriggerContext| {
1072            let executed_clone = executed_clone.clone();
1073            async move {
1074                executed_clone
1075                    .write()
1076                    .unwrap()
1077                    .push(ctx.rule_id.clone());
1078                Ok(())
1079            }
1080        });
1081
1082        engine.add_rule(make_rule(
1083            "r1",
1084            "user.*",
1085            "collect",
1086            serde_json::json!({}),
1087        ));
1088        engine.add_rule(make_rule(
1089            "r2",
1090            "order.*",
1091            "collect",
1092            serde_json::json!({}),
1093        ));
1094
1095        let results = engine
1096            .process_event("user.created", &serde_json::json!({}))
1097            .await;
1098
1099        assert_eq!(results.len(), 1);
1100        assert!(results[0].is_ok());
1101        let executed = executed.read().unwrap();
1102        assert_eq!(executed.len(), 1);
1103        assert_eq!(executed[0], "r1");
1104    }
1105
1106    #[tokio::test]
1107    async fn test_process_event_with_condition() {
1108        let bus = EventBus::new();
1109        let engine = TriggerRuleEngine::new(bus);
1110
1111        let executed: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(Vec::new()));
1112        let executed_clone = executed.clone();
1113
1114        engine.register_action("collect", move |ctx: TriggerContext| {
1115            let executed_clone = executed_clone.clone();
1116            async move {
1117                executed_clone
1118                    .write()
1119                    .unwrap()
1120                    .push(ctx.rule_id.clone());
1121                Ok(())
1122            }
1123        });
1124
1125        let mut rule = make_rule("r1", "order.*", "collect", serde_json::json!({}));
1126        rule.condition = Some(serde_json::json!({"amount": {"$gt": 100}}));
1127        engine.add_rule(rule);
1128
1129        // 不满足条件
1130        let results = engine
1131            .process_event("order.created", &serde_json::json!({"amount": 50}))
1132            .await;
1133        assert_eq!(results.len(), 0);
1134
1135        // 满足条件
1136        let results = engine
1137            .process_event("order.created", &serde_json::json!({"amount": 200}))
1138            .await;
1139        assert_eq!(results.len(), 1);
1140    }
1141
1142    #[tokio::test]
1143    async fn test_process_event_disabled_rule() {
1144        let bus = EventBus::new();
1145        let engine = TriggerRuleEngine::new(bus);
1146
1147        let executed: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(Vec::new()));
1148        let executed_clone = executed.clone();
1149
1150        engine.register_action("collect", move |ctx: TriggerContext| {
1151            let executed_clone = executed_clone.clone();
1152            async move {
1153                executed_clone
1154                    .write()
1155                    .unwrap()
1156                    .push(ctx.rule_id.clone());
1157                Ok(())
1158            }
1159        });
1160
1161        let mut rule = make_rule("r1", "user.*", "collect", serde_json::json!({}));
1162        rule.enabled = false;
1163        engine.add_rule(rule);
1164
1165        let results = engine
1166            .process_event("user.created", &serde_json::json!({}))
1167            .await;
1168        assert_eq!(results.len(), 0);
1169    }
1170
1171    #[tokio::test]
1172    async fn test_process_event_unknown_action() {
1173        let bus = EventBus::new();
1174        let engine = TriggerRuleEngine::new(bus);
1175
1176        // No action registered
1177        engine.add_rule(make_rule("r1", "user.*", "unknown_action", serde_json::json!({})));
1178
1179        let results = engine
1180            .process_event("user.created", &serde_json::json!({}))
1181            .await;
1182        assert_eq!(results.len(), 0); // Unknown action is skipped with a warning
1183    }
1184
1185    #[tokio::test]
1186    async fn test_process_event_wildcard_pattern() {
1187        let bus = EventBus::new();
1188        let engine = TriggerRuleEngine::new(bus);
1189
1190        let executed: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(Vec::new()));
1191        let executed_clone = executed.clone();
1192
1193        engine.register_action("collect", move |ctx: TriggerContext| {
1194            let executed_clone = executed_clone.clone();
1195            async move {
1196                executed_clone
1197                    .write()
1198                    .unwrap()
1199                    .push(ctx.rule_id.clone());
1200                Ok(())
1201            }
1202        });
1203
1204        engine.add_rule(make_rule("r1", "user.**", "collect", serde_json::json!({})));
1205
1206        // 多层路径匹配
1207        let results = engine
1208            .process_event("user.profile.updated", &serde_json::json!({}))
1209            .await;
1210        assert_eq!(results.len(), 1);
1211    }
1212
1213    #[test]
1214    fn test_trigger_rule_serialization() {
1215        let rule = TriggerRule {
1216            id: "rule-1".to_string(),
1217            name: "Test Rule".to_string(),
1218            event_pattern: "user.*".to_string(),
1219            condition: Some(serde_json::json!({"status": {"$eq": "active"}})),
1220            action_type: "notify".to_string(),
1221            action_config: serde_json::json!({"channel": "email"}),
1222            enabled: true,
1223            priority: 0,
1224        };
1225
1226        let json = serde_json::to_string(&rule).unwrap();
1227        let deserialized: TriggerRule = serde_json::from_str(&json).unwrap();
1228        assert_eq!(deserialized.id, "rule-1");
1229        assert_eq!(deserialized.event_pattern, "user.*");
1230        assert!(deserialized.condition.is_some());
1231    }
1232
1233    // ── start/stop tests ──────────────────────────────────────
1234
1235    #[tokio::test]
1236    async fn test_start_stop() {
1237        let bus = EventBus::new();
1238        let engine = TriggerRuleEngine::new(bus);
1239
1240        assert!(!engine.is_running());
1241
1242        engine.start().await.unwrap();
1243        assert!(engine.is_running());
1244
1245        // Starting again is a no-op
1246        engine.start().await.unwrap();
1247        assert!(engine.is_running());
1248
1249        engine.stop();
1250        assert!(!engine.is_running());
1251    }
1252
1253    #[tokio::test]
1254    async fn test_process_event_works_regardless_of_running_state() {
1255        // process_event is a direct API for manual/testing use and should work
1256        // regardless of the running flag. The running flag only controls whether
1257        // the publish callback spawns processing tasks.
1258        let bus = EventBus::new();
1259        let engine = TriggerRuleEngine::new(bus);
1260
1261        let executed: Arc<RwLock<Vec<String>>> = Arc::new(RwLock::new(Vec::new()));
1262        let executed_clone = executed.clone();
1263
1264        engine.register_action("collect", move |ctx: TriggerContext| {
1265            let executed_clone = executed_clone.clone();
1266            async move {
1267                executed_clone
1268                    .write()
1269                    .unwrap()
1270                    .push(ctx.rule_id.clone());
1271                Ok(())
1272            }
1273        });
1274
1275        engine.add_rule(make_rule("r1", "user.*", "collect", serde_json::json!({})));
1276
1277        // Engine was never started (running = false), but process_event still works
1278        let results = engine
1279            .process_event("user.created", &serde_json::json!({}))
1280            .await;
1281        assert_eq!(results.len(), 1);
1282        assert!(results[0].is_ok());
1283        let log = executed.read().unwrap();
1284        assert_eq!(log.len(), 1);
1285        assert_eq!(log[0], "r1");
1286    }
1287
1288    // ── RuleStorage tests ───────────────────────────────────────
1289
1290    #[test]
1291    fn test_in_memory_rule_storage_basic() {
1292        let storage = InMemoryRuleStorage::new();
1293
1294        storage.add(make_rule("r1", "user.*", "log", serde_json::json!({})));
1295        storage.add(make_rule("r2", "order.*", "notify", serde_json::json!({})));
1296
1297        assert_eq!(storage.count(), 2);
1298
1299        let rules = storage.list();
1300        assert_eq!(rules.len(), 2);
1301
1302        assert!(storage.get("r1").is_some());
1303        assert!(storage.get("nonexistent").is_none());
1304
1305        let removed = storage.remove("r1").unwrap();
1306        assert_eq!(removed.id, "r1");
1307        assert_eq!(storage.count(), 1);
1308        assert!(storage.remove("nonexistent").is_none());
1309
1310        let mut updated = make_rule("r2", "order.**", "email", serde_json::json!({}));
1311        updated.name = "Updated".to_string();
1312        assert!(storage.update(updated));
1313        assert_eq!(storage.get("r2").unwrap().name, "Updated");
1314        assert!(!storage.update(make_rule("r99", "x", "y", serde_json::json!({}))));
1315    }
1316
1317    #[test]
1318    fn test_with_custom_storage() {
1319        let storage: Arc<dyn RuleStorage> = Arc::new(InMemoryRuleStorage::new());
1320        let bus = EventBus::new();
1321        let engine = TriggerRuleEngine::with_storage(bus, storage.clone());
1322
1323        engine.add_rule(make_rule("r1", "user.*", "log", serde_json::json!({})));
1324
1325        // Verify through storage directly
1326        assert_eq!(storage.count(), 1);
1327        assert_eq!(storage.get("r1").unwrap().event_pattern, "user.*");
1328
1329        // Verify through engine
1330        assert_eq!(engine.rule_count(), 1);
1331        assert_eq!(engine.list_rules()[0].id, "r1");
1332    }
1333}