clia_config_expr/
lib.rs

1use regex::Regex;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use thiserror::Error;
5
6/// 配置表达式错误类型
7#[derive(Error, Debug)]
8pub enum ConfigExprError {
9    #[error("Invalid operator: {0}")]
10    InvalidOperator(String),
11    #[error("Field not found: {0}")]
12    FieldNotFound(String),
13    #[error("Regex compilation error: {0}")]
14    RegexError(#[from] regex::Error),
15    #[error("JSON serialization error: {0}")]
16    JsonError(#[from] serde_json::Error),
17    #[error("Validation error: {0}")]
18    ValidationError(String),
19}
20
21/// 操作符枚举
22#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23#[serde(rename_all = "lowercase")]
24pub enum Operator {
25    Equals,
26    Contains,
27    Prefix,
28    Suffix,
29    Regex,
30    #[serde(rename = "gt")]
31    GreaterThan,
32    #[serde(rename = "lt")]
33    LessThan,
34    #[serde(rename = "ge")]
35    GreaterThanOrEqual,
36    #[serde(rename = "le")]
37    LessThanOrEqual,
38}
39
40impl Operator {
41    /// 验证操作符是否有效
42    pub fn is_valid(&self) -> bool {
43        matches!(
44            self,
45            Operator::Equals
46                | Operator::Contains
47                | Operator::Prefix
48                | Operator::Suffix
49                | Operator::Regex
50                | Operator::GreaterThan
51                | Operator::LessThan
52                | Operator::GreaterThanOrEqual
53                | Operator::LessThanOrEqual
54        )
55    }
56}
57
58/// 条件表达式
59#[derive(Debug, Clone, Serialize, Deserialize)]
60#[serde(untagged)]
61pub enum Condition {
62    /// 简单条件:字段比较
63    Simple {
64        field: String,
65        op: Operator,
66        value: String,
67    },
68    /// AND 条件:所有子条件都必须满足
69    And { and: Vec<Condition> },
70    /// OR 条件:至少一个子条件满足
71    Or { or: Vec<Condition> },
72}
73
74/// 规则的返回值,支持字符串或JSON对象
75#[derive(Debug, Clone, Serialize, Deserialize)]
76#[serde(untagged)]
77pub enum RuleResult {
78    String(String),
79    Object(serde_json::Value),
80}
81
82/// 单个规则定义
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct Rule {
85    #[serde(rename = "if")]
86    pub condition: Condition,
87    #[serde(rename = "then")]
88    pub result: RuleResult,
89}
90
91/// 配置规则集
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct ConfigRules {
94    pub rules: Vec<Rule>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub fallback: Option<RuleResult>,
97}
98
99/// 配置表达式评估器
100pub struct ConfigEvaluator {
101    rules: ConfigRules,
102}
103
104impl ConfigEvaluator {
105    /// 创建新的评估器
106    pub fn new(rules: ConfigRules) -> Result<Self, ConfigExprError> {
107        // 验证规则集
108        Self::validate_rules(&rules)?;
109        Ok(Self { rules })
110    }
111
112    /// 从JSON字符串创建评估器
113    pub fn from_json(json: &str) -> Result<Self, ConfigExprError> {
114        let rules: ConfigRules = serde_json::from_str(json)?;
115        Self::new(rules)
116    }
117
118    /// 评估请求参数,返回匹配的结果
119    pub fn evaluate(&self, params: &HashMap<String, String>) -> Option<RuleResult> {
120        for rule in &self.rules.rules {
121            if self.evaluate_condition(&rule.condition, params) {
122                return Some(rule.result.clone());
123            }
124        }
125        self.rules.fallback.clone()
126    }
127
128    /// 评估单个条件
129    fn evaluate_condition(&self, condition: &Condition, params: &HashMap<String, String>) -> bool {
130        match condition {
131            Condition::Simple { field, op, value } => {
132                self.evaluate_simple_condition(field, op, value, params)
133            }
134            Condition::And { and } => and.iter().all(|cond| self.evaluate_condition(cond, params)),
135            Condition::Or { or } => or.iter().any(|cond| self.evaluate_condition(cond, params)),
136        }
137    }
138
139    /// 评估简单条件
140    fn evaluate_simple_condition(
141        &self,
142        field: &str,
143        op: &Operator,
144        value: &str,
145        params: &HashMap<String, String>,
146    ) -> bool {
147        let field_value = match params.get(field) {
148            Some(v) => v,
149            None => return false,
150        };
151
152        match op {
153            Operator::Equals => field_value == value,
154            Operator::Contains => field_value.contains(value),
155            Operator::Prefix => field_value.starts_with(value),
156            Operator::Suffix => field_value.ends_with(value),
157            Operator::Regex => {
158                match Regex::new(value) {
159                    Ok(regex) => regex.is_match(field_value),
160                    Err(_) => false, // 正则表达式无效时返回false
161                }
162            }
163            Operator::GreaterThan => self.compare_numbers(field_value, value, |a, b| a > b),
164            Operator::LessThan => self.compare_numbers(field_value, value, |a, b| a < b),
165            Operator::GreaterThanOrEqual => self.compare_numbers(field_value, value, |a, b| a >= b),
166            Operator::LessThanOrEqual => self.compare_numbers(field_value, value, |a, b| a <= b),
167        }
168    }
169
170    /// 比较两个字符串作为数字
171    fn compare_numbers<F>(&self, field_value: &str, target_value: &str, compare_fn: F) -> bool
172    where
173        F: Fn(f64, f64) -> bool,
174    {
175        match (field_value.parse::<f64>(), target_value.parse::<f64>()) {
176            (Ok(field_num), Ok(target_num)) => compare_fn(field_num, target_num),
177            _ => false, // 如果任一值无法解析为数字,返回false
178        }
179    }
180
181    /// 验证规则集是否合法
182    fn validate_rules(rules: &ConfigRules) -> Result<(), ConfigExprError> {
183        if rules.rules.is_empty() {
184            return Err(ConfigExprError::ValidationError(
185                "Rules cannot be empty".to_string(),
186            ));
187        }
188
189        for (index, rule) in rules.rules.iter().enumerate() {
190            Self::validate_condition(&rule.condition, index)?;
191        }
192
193        Ok(())
194    }
195
196    /// 验证条件是否合法
197    fn validate_condition(condition: &Condition, rule_index: usize) -> Result<(), ConfigExprError> {
198        match condition {
199            Condition::Simple { field, op, value } => {
200                if field.is_empty() {
201                    return Err(ConfigExprError::ValidationError(format!(
202                        "Field name cannot be empty in rule {}",
203                        rule_index
204                    )));
205                }
206
207                if !op.is_valid() {
208                    return Err(ConfigExprError::InvalidOperator(format!("{:?}", op)));
209                }
210
211                // 验证正则表达式
212                if matches!(op, Operator::Regex) {
213                    Regex::new(value).map_err(|e| {
214                        ConfigExprError::ValidationError(format!(
215                            "Invalid regex '{}' in rule {}: {}",
216                            value, rule_index, e
217                        ))
218                    })?;
219                }
220            }
221            Condition::And { and } => {
222                if and.is_empty() {
223                    return Err(ConfigExprError::ValidationError(format!(
224                        "AND condition cannot be empty in rule {}",
225                        rule_index
226                    )));
227                }
228                for cond in and {
229                    Self::validate_condition(cond, rule_index)?;
230                }
231            }
232            Condition::Or { or } => {
233                if or.is_empty() {
234                    return Err(ConfigExprError::ValidationError(format!(
235                        "OR condition cannot be empty in rule {}",
236                        rule_index
237                    )));
238                }
239                for cond in or {
240                    Self::validate_condition(cond, rule_index)?;
241                }
242            }
243        }
244        Ok(())
245    }
246}
247
248/// 便利方法:直接从JSON字符串评估
249pub fn evaluate_json(
250    json: &str,
251    params: &HashMap<String, String>,
252) -> Result<Option<RuleResult>, ConfigExprError> {
253    let evaluator = ConfigEvaluator::from_json(json)?;
254    Ok(evaluator.evaluate(params))
255}
256
257/// 便利方法:验证JSON规则是否合法
258pub fn validate_json(json: &str) -> Result<(), ConfigExprError> {
259    let rules: ConfigRules = serde_json::from_str(json)?;
260    ConfigEvaluator::validate_rules(&rules)
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    #[test]
268    fn test_simple_condition() {
269        let json = r#"
270        {
271            "rules": [
272                {
273                    "if": {
274                        "field": "platform",
275                        "op": "equals",
276                        "value": "RTD"
277                    },
278                    "then": "chip_rtd"
279                }
280            ]
281        }
282        "#;
283
284        let mut params = HashMap::new();
285        params.insert("platform".to_string(), "RTD".to_string());
286
287        let result = evaluate_json(json, &params).unwrap();
288        assert!(result.is_some());
289
290        if let Some(RuleResult::String(s)) = result {
291            assert_eq!(s, "chip_rtd");
292        } else {
293            panic!("Expected string result");
294        }
295    }
296
297    #[test]
298    fn test_and_condition() {
299        let json = r#"
300        {
301            "rules": [
302                {
303                    "if": {
304                        "and": [
305                            { "field": "platform", "op": "contains", "value": "RTD" },
306                            { "field": "region", "op": "equals", "value": "CN" }
307                        ]
308                    },
309                    "then": "chip_rtd_cn"
310                }
311            ]
312        }
313        "#;
314
315        let mut params = HashMap::new();
316        params.insert("platform".to_string(), "RTD-2000".to_string());
317        params.insert("region".to_string(), "CN".to_string());
318
319        let result = evaluate_json(json, &params).unwrap();
320        assert!(result.is_some());
321
322        if let Some(RuleResult::String(s)) = result {
323            assert_eq!(s, "chip_rtd_cn");
324        } else {
325            panic!("Expected string result");
326        }
327    }
328
329    #[test]
330    fn test_or_condition() {
331        let json = r#"
332        {
333            "rules": [
334                {
335                    "if": {
336                        "or": [
337                            { "field": "platform", "op": "equals", "value": "MT9950" },
338                            { "field": "platform", "op": "equals", "value": "MT9638" }
339                        ]
340                    },
341                    "then": "chip_mt"
342                }
343            ]
344        }
345        "#;
346
347        let mut params = HashMap::new();
348        params.insert("platform".to_string(), "MT9950".to_string());
349
350        let result = evaluate_json(json, &params).unwrap();
351        assert!(result.is_some());
352
353        if let Some(RuleResult::String(s)) = result {
354            assert_eq!(s, "chip_mt");
355        } else {
356            panic!("Expected string result");
357        }
358    }
359
360    #[test]
361    fn test_prefix_condition() {
362        let json = r#"
363        {
364            "rules": [
365                {
366                    "if": {
367                        "field": "platform",
368                        "op": "prefix",
369                        "value": "Hi"
370                    },
371                    "then": "chip_hi"
372                }
373            ]
374        }
375        "#;
376
377        let mut params = HashMap::new();
378        params.insert("platform".to_string(), "Hi3516".to_string());
379
380        let result = evaluate_json(json, &params).unwrap();
381        assert!(result.is_some());
382
383        if let Some(RuleResult::String(s)) = result {
384            assert_eq!(s, "chip_hi");
385        } else {
386            panic!("Expected string result");
387        }
388    }
389
390    #[test]
391    fn test_json_object_result() {
392        let json = r#"
393        {
394            "rules": [
395                {
396                    "if": {
397                        "field": "platform",
398                        "op": "equals",
399                        "value": "RTD"
400                    },
401                    "then": {
402                        "chip": "rtd",
403                        "config": {
404                            "memory": "2GB",
405                            "cpu": "ARM"
406                        }
407                    }
408                }
409            ]
410        }
411        "#;
412
413        let mut params = HashMap::new();
414        params.insert("platform".to_string(), "RTD".to_string());
415
416        let result = evaluate_json(json, &params).unwrap();
417        assert!(result.is_some());
418
419        if let Some(RuleResult::Object(obj)) = result {
420            assert_eq!(obj["chip"], "rtd");
421            assert_eq!(obj["config"]["memory"], "2GB");
422        } else {
423            panic!("Expected object result");
424        }
425    }
426
427    #[test]
428    fn test_fallback() {
429        let json = r#"
430        {
431            "rules": [
432                {
433                    "if": {
434                        "field": "platform",
435                        "op": "equals",
436                        "value": "RTD"
437                    },
438                    "then": "chip_rtd"
439                }
440            ],
441            "fallback": "default_chip"
442        }
443        "#;
444
445        let mut params = HashMap::new();
446        params.insert("platform".to_string(), "Unknown".to_string());
447
448        let result = evaluate_json(json, &params).unwrap();
449        assert!(result.is_some());
450
451        if let Some(RuleResult::String(s)) = result {
452            assert_eq!(s, "default_chip");
453        } else {
454            panic!("Expected string result");
455        }
456    }
457
458    #[test]
459    fn test_no_match_no_fallback() {
460        let json = r#"
461        {
462            "rules": [
463                {
464                    "if": {
465                        "field": "platform",
466                        "op": "equals",
467                        "value": "RTD"
468                    },
469                    "then": "chip_rtd"
470                }
471            ]
472        }
473        "#;
474
475        let mut params = HashMap::new();
476        params.insert("platform".to_string(), "Unknown".to_string());
477
478        let result = evaluate_json(json, &params).unwrap();
479        assert!(result.is_none());
480    }
481
482    #[test]
483    fn test_regex_condition() {
484        let json = r#"
485        {
486            "rules": [
487                {
488                    "if": {
489                        "field": "platform",
490                        "op": "regex",
491                        "value": "^Hi\\d+"
492                    },
493                    "then": "chip_hi"
494                }
495            ]
496        }
497        "#;
498
499        let mut params = HashMap::new();
500        params.insert("platform".to_string(), "Hi3516".to_string());
501
502        let result = evaluate_json(json, &params).unwrap();
503        assert!(result.is_some());
504
505        if let Some(RuleResult::String(s)) = result {
506            assert_eq!(s, "chip_hi");
507        } else {
508            panic!("Expected string result");
509        }
510    }
511
512    #[test]
513    fn test_suffix_condition() {
514        let json = r#"
515        {
516            "rules": [
517                {
518                    "if": {
519                        "field": "platform",
520                        "op": "suffix",
521                        "value": "Pro"
522                    },
523                    "then": "chip_pro"
524                }
525            ]
526        }
527        "#;
528
529        let mut params = HashMap::new();
530        params.insert("platform".to_string(), "RTD-Pro".to_string());
531
532        let result = evaluate_json(json, &params).unwrap();
533        assert!(result.is_some());
534
535        if let Some(RuleResult::String(s)) = result {
536            assert_eq!(s, "chip_pro");
537        } else {
538            panic!("Expected string result");
539        }
540    }
541
542    #[test]
543    fn test_validation_empty_rules() {
544        let json = r#"
545        {
546            "rules": []
547        }
548        "#;
549
550        let result = validate_json(json);
551        assert!(result.is_err());
552        assert!(result
553            .unwrap_err()
554            .to_string()
555            .contains("Rules cannot be empty"));
556    }
557
558    #[test]
559    fn test_validation_empty_field() {
560        let json = r#"
561        {
562            "rules": [
563                {
564                    "if": {
565                        "field": "",
566                        "op": "equals",
567                        "value": "RTD"
568                    },
569                    "then": "chip_rtd"
570                }
571            ]
572        }
573        "#;
574
575        let result = validate_json(json);
576        assert!(result.is_err());
577        assert!(result
578            .unwrap_err()
579            .to_string()
580            .contains("Field name cannot be empty"));
581    }
582
583    #[test]
584    fn test_validation_invalid_regex() {
585        let json = r#"
586        {
587            "rules": [
588                {
589                    "if": {
590                        "field": "platform",
591                        "op": "regex",
592                        "value": "[invalid"
593                    },
594                    "then": "chip_rtd"
595                }
596            ]
597        }
598        "#;
599
600        let result = validate_json(json);
601        assert!(result.is_err());
602        assert!(result.unwrap_err().to_string().contains("Invalid regex"));
603    }
604
605    #[test]
606    fn test_validation_empty_and_condition() {
607        let json = r#"
608        {
609            "rules": [
610                {
611                    "if": {
612                        "and": []
613                    },
614                    "then": "chip_rtd"
615                }
616            ]
617        }
618        "#;
619
620        let result = validate_json(json);
621        assert!(result.is_err());
622        assert!(result
623            .unwrap_err()
624            .to_string()
625            .contains("AND condition cannot be empty"));
626    }
627
628    #[test]
629    fn test_validation_empty_or_condition() {
630        let json = r#"
631        {
632            "rules": [
633                {
634                    "if": {
635                        "or": []
636                    },
637                    "then": "chip_rtd"
638                }
639            ]
640        }
641        "#;
642
643        let result = validate_json(json);
644        assert!(result.is_err());
645        assert!(result
646            .unwrap_err()
647            .to_string()
648            .contains("OR condition cannot be empty"));
649    }
650
651    #[test]
652    fn test_complex_nested_conditions() {
653        let json = r#"
654        {
655            "rules": [
656                {
657                    "if": {
658                        "and": [
659                            {
660                                "or": [
661                                    { "field": "platform", "op": "prefix", "value": "Hi" },
662                                    { "field": "platform", "op": "prefix", "value": "MT" }
663                                ]
664                            },
665                            { "field": "region", "op": "equals", "value": "CN" }
666                        ]
667                    },
668                    "then": "chip_cn"
669                }
670            ]
671        }
672        "#;
673
674        let mut params = HashMap::new();
675        params.insert("platform".to_string(), "Hi3516".to_string());
676        params.insert("region".to_string(), "CN".to_string());
677
678        let result = evaluate_json(json, &params).unwrap();
679        assert!(result.is_some());
680
681        if let Some(RuleResult::String(s)) = result {
682            assert_eq!(s, "chip_cn");
683        } else {
684            panic!("Expected string result");
685        }
686    }
687
688    #[test]
689    fn test_greater_than_condition() {
690        let json = r#"
691        {
692            "rules": [
693                {
694                    "if": {
695                        "field": "score",
696                        "op": "gt",
697                        "value": "80"
698                    },
699                    "then": "high_score"
700                }
701            ]
702        }
703        "#;
704
705        let mut params = HashMap::new();
706        params.insert("score".to_string(), "85".to_string());
707
708        let result = evaluate_json(json, &params).unwrap();
709        assert!(result.is_some());
710
711        if let Some(RuleResult::String(s)) = result {
712            assert_eq!(s, "high_score");
713        } else {
714            panic!("Expected string result");
715        }
716
717        // 测试不满足条件的情况
718        let mut params = HashMap::new();
719        params.insert("score".to_string(), "75".to_string());
720
721        let result = evaluate_json(json, &params).unwrap();
722        assert!(result.is_none());
723    }
724
725    #[test]
726    fn test_less_than_condition() {
727        let json = r#"
728        {
729            "rules": [
730                {
731                    "if": {
732                        "field": "age",
733                        "op": "lt",
734                        "value": "18"
735                    },
736                    "then": "minor"
737                }
738            ]
739        }
740        "#;
741
742        let mut params = HashMap::new();
743        params.insert("age".to_string(), "16".to_string());
744
745        let result = evaluate_json(json, &params).unwrap();
746        assert!(result.is_some());
747
748        if let Some(RuleResult::String(s)) = result {
749            assert_eq!(s, "minor");
750        } else {
751            panic!("Expected string result");
752        }
753    }
754
755    #[test]
756    fn test_greater_than_or_equal_condition() {
757        let json = r#"
758        {
759            "rules": [
760                {
761                    "if": {
762                        "field": "level",
763                        "op": "ge",
764                        "value": "5"
765                    },
766                    "then": "advanced"
767                }
768            ]
769        }
770        "#;
771
772        let mut params = HashMap::new();
773        params.insert("level".to_string(), "5".to_string());
774
775        let result = evaluate_json(json, &params).unwrap();
776        assert!(result.is_some());
777
778        if let Some(RuleResult::String(s)) = result {
779            assert_eq!(s, "advanced");
780        } else {
781            panic!("Expected string result");
782        }
783    }
784
785    #[test]
786    fn test_less_than_or_equal_condition() {
787        let json = r#"
788        {
789            "rules": [
790                {
791                    "if": {
792                        "field": "temperature",
793                        "op": "le",
794                        "value": "25.5"
795                    },
796                    "then": "cool"
797                }
798            ]
799        }
800        "#;
801
802        let mut params = HashMap::new();
803        params.insert("temperature".to_string(), "23.8".to_string());
804
805        let result = evaluate_json(json, &params).unwrap();
806        assert!(result.is_some());
807
808        if let Some(RuleResult::String(s)) = result {
809            assert_eq!(s, "cool");
810        } else {
811            panic!("Expected string result");
812        }
813    }
814
815    #[test]
816    fn test_numeric_comparison_with_invalid_numbers() {
817        let json = r#"
818        {
819            "rules": [
820                {
821                    "if": {
822                        "field": "value",
823                        "op": "gt",
824                        "value": "10"
825                    },
826                    "then": "valid"
827                }
828            ],
829            "fallback": "invalid"
830        }
831        "#;
832
833        // 测试无效数字
834        let mut params = HashMap::new();
835        params.insert("value".to_string(), "not_a_number".to_string());
836
837        let result = evaluate_json(json, &params).unwrap();
838        assert!(result.is_some());
839
840        if let Some(RuleResult::String(s)) = result {
841            assert_eq!(s, "invalid"); // 应该使用fallback
842        } else {
843            panic!("Expected string result");
844        }
845    }
846
847    #[test]
848    fn test_numeric_comparison_with_decimal_numbers() {
849        let json = r#"
850        {
851            "rules": [
852                {
853                    "if": {
854                        "field": "price",
855                        "op": "ge",
856                        "value": "99.99"
857                    },
858                    "then": "expensive"
859                }
860            ]
861        }
862        "#;
863
864        let mut params = HashMap::new();
865        params.insert("price".to_string(), "100.50".to_string());
866
867        let result = evaluate_json(json, &params).unwrap();
868        assert!(result.is_some());
869
870        if let Some(RuleResult::String(s)) = result {
871            assert_eq!(s, "expensive");
872        } else {
873            panic!("Expected string result");
874        }
875    }
876}