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