Skip to main content

lemma/evaluation/
response.rs

1use crate::evaluation::operations::{OperationRecord, OperationResult};
2use crate::planning::semantics::{Expression, Fact, LemmaType, RulePath, Source};
3use indexmap::IndexMap;
4use serde::Serialize;
5
6/// Rule info with resolved expressions for use in evaluation response.
7/// Evaluation uses only semantics types; no parsing types.
8#[derive(Debug, Clone, Serialize)]
9pub struct EvaluatedRule {
10    pub name: String,
11    pub path: RulePath,
12    pub default_expression: Expression,
13    pub unless_branches: Vec<(Option<Expression>, Expression)>,
14    pub source_location: Source,
15    pub rule_type: LemmaType,
16}
17
18/// Facts from a specific spec (semantics types only).
19#[derive(Debug, Clone, Serialize)]
20pub struct Facts {
21    pub fact_path: String,
22    pub referencing_fact_name: String,
23    pub facts: Vec<Fact>,
24}
25
26/// Response from evaluating a Lemma spec
27#[derive(Debug, Clone, Serialize)]
28pub struct Response {
29    pub spec_name: String,
30    pub facts: Vec<Facts>,
31    pub results: IndexMap<String, RuleResult>,
32}
33
34/// Result of evaluating a single rule (semantics types only).
35#[derive(Debug, Clone, Serialize)]
36pub struct RuleResult {
37    #[serde(skip_serializing)]
38    pub rule: EvaluatedRule,
39    pub result: OperationResult,
40    pub facts: Vec<Fact>,
41    #[serde(skip_serializing)]
42    pub operations: Vec<OperationRecord>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub proof: Option<crate::evaluation::proof::Proof>,
45    /// Computed type of this rule's result (semantics).
46    pub rule_type: LemmaType,
47}
48
49impl Response {
50    pub fn add_result(&mut self, result: RuleResult) {
51        self.results.insert(result.rule.name.clone(), result);
52    }
53
54    pub fn filter_rules(&mut self, rule_names: &[String]) {
55        self.results.retain(|name, _| rule_names.contains(name));
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use crate::planning::semantics::{
63        primitive_boolean, primitive_number, Expression, ExpressionKind, LemmaType, LiteralValue,
64        RulePath, Span,
65    };
66    use rust_decimal::Decimal;
67    use std::str::FromStr;
68
69    fn dummy_source() -> Source {
70        Source::new(
71            "test",
72            Span {
73                start: 0,
74                end: 0,
75                line: 1,
76                col: 1,
77            },
78            std::sync::Arc::from("spec test_spec\nfact x: 1\nrule result: x"),
79        )
80    }
81
82    fn dummy_evaluated_rule(name: &str) -> EvaluatedRule {
83        EvaluatedRule {
84            name: name.to_string(),
85            path: RulePath::new(vec![], name.to_string()),
86            default_expression: Expression::new(
87                ExpressionKind::Literal(Box::new(LiteralValue::from_bool(true))),
88                dummy_source(),
89            ),
90            unless_branches: vec![],
91            source_location: dummy_source(),
92            rule_type: primitive_number().clone(),
93        }
94    }
95
96    #[test]
97    fn test_response_serialization() {
98        let mut results = IndexMap::new();
99        results.insert(
100            "test_rule".to_string(),
101            RuleResult {
102                rule: dummy_evaluated_rule("test_rule"),
103                result: OperationResult::Value(Box::new(LiteralValue::number(
104                    Decimal::from_str("42").unwrap(),
105                ))),
106                facts: vec![],
107                operations: vec![],
108                proof: None,
109                rule_type: primitive_number().clone(),
110            },
111        );
112        let response = Response {
113            spec_name: "test_spec".to_string(),
114            facts: vec![],
115            results,
116        };
117
118        let json = serde_json::to_string(&response).unwrap();
119        assert!(json.contains("test_spec"));
120        assert!(json.contains("test_rule"));
121        assert!(json.contains("results"));
122    }
123
124    #[test]
125    fn test_response_filter_rules() {
126        let mut results = IndexMap::new();
127        results.insert(
128            "rule1".to_string(),
129            RuleResult {
130                rule: dummy_evaluated_rule("rule1"),
131                result: OperationResult::Value(Box::new(LiteralValue::from_bool(true))),
132                facts: vec![],
133                operations: vec![],
134                proof: None,
135                rule_type: primitive_boolean().clone(),
136            },
137        );
138        results.insert(
139            "rule2".to_string(),
140            RuleResult {
141                rule: dummy_evaluated_rule("rule2"),
142                result: OperationResult::Value(Box::new(LiteralValue::from_bool(false))),
143                facts: vec![],
144                operations: vec![],
145                proof: None,
146                rule_type: primitive_boolean().clone(),
147            },
148        );
149        let mut response = Response {
150            spec_name: "test_spec".to_string(),
151            facts: vec![],
152            results,
153        };
154
155        response.filter_rules(&["rule1".to_string()]);
156
157        assert_eq!(response.results.len(), 1);
158        assert_eq!(response.results.values().next().unwrap().rule.name, "rule1");
159    }
160
161    #[test]
162    fn test_rule_result_types() {
163        let success = RuleResult {
164            rule: dummy_evaluated_rule("rule1"),
165            result: OperationResult::Value(Box::new(LiteralValue::from_bool(true))),
166            facts: vec![],
167            operations: vec![],
168            proof: None,
169            rule_type: primitive_boolean().clone(),
170        };
171        assert!(matches!(success.result, OperationResult::Value(_)));
172
173        let missing = RuleResult {
174            rule: dummy_evaluated_rule("rule3"),
175            result: OperationResult::Veto(Some("Missing fact: fact1".to_string())),
176            facts: vec![crate::planning::semantics::Fact {
177                path: crate::planning::semantics::FactPath::new(vec![], "fact1".to_string()),
178                value: crate::planning::semantics::FactValue::Literal(
179                    crate::planning::semantics::LiteralValue::from_bool(false),
180                ),
181                source: None,
182            }],
183            operations: vec![],
184            proof: None,
185            rule_type: LemmaType::veto_type(),
186        };
187        assert_eq!(missing.facts.len(), 1);
188        assert_eq!(missing.facts[0].path.fact, "fact1");
189        assert!(matches!(missing.result, OperationResult::Veto(_)));
190
191        let veto = RuleResult {
192            rule: dummy_evaluated_rule("rule4"),
193            result: OperationResult::Veto(Some("Vetoed".to_string())),
194            facts: vec![],
195            operations: vec![],
196            proof: None,
197            rule_type: LemmaType::veto_type(),
198        };
199        assert_eq!(
200            veto.result,
201            OperationResult::Veto(Some("Vetoed".to_string()))
202        );
203    }
204}