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            "test_spec",
79            std::sync::Arc::from("spec test_spec\nfact x: 1\nrule result: x"),
80        )
81    }
82
83    fn dummy_evaluated_rule(name: &str) -> EvaluatedRule {
84        EvaluatedRule {
85            name: name.to_string(),
86            path: RulePath::new(vec![], name.to_string()),
87            default_expression: Expression::new(
88                ExpressionKind::Literal(Box::new(LiteralValue::from_bool(true))),
89                dummy_source(),
90            ),
91            unless_branches: vec![],
92            source_location: dummy_source(),
93            rule_type: primitive_number().clone(),
94        }
95    }
96
97    #[test]
98    fn test_response_serialization() {
99        let mut results = IndexMap::new();
100        results.insert(
101            "test_rule".to_string(),
102            RuleResult {
103                rule: dummy_evaluated_rule("test_rule"),
104                result: OperationResult::Value(Box::new(LiteralValue::number(
105                    Decimal::from_str("42").unwrap(),
106                ))),
107                facts: vec![],
108                operations: vec![],
109                proof: None,
110                rule_type: primitive_number().clone(),
111            },
112        );
113        let response = Response {
114            spec_name: "test_spec".to_string(),
115            facts: vec![],
116            results,
117        };
118
119        let json = serde_json::to_string(&response).unwrap();
120        assert!(json.contains("test_spec"));
121        assert!(json.contains("test_rule"));
122        assert!(json.contains("results"));
123    }
124
125    #[test]
126    fn test_response_filter_rules() {
127        let mut results = IndexMap::new();
128        results.insert(
129            "rule1".to_string(),
130            RuleResult {
131                rule: dummy_evaluated_rule("rule1"),
132                result: OperationResult::Value(Box::new(LiteralValue::from_bool(true))),
133                facts: vec![],
134                operations: vec![],
135                proof: None,
136                rule_type: primitive_boolean().clone(),
137            },
138        );
139        results.insert(
140            "rule2".to_string(),
141            RuleResult {
142                rule: dummy_evaluated_rule("rule2"),
143                result: OperationResult::Value(Box::new(LiteralValue::from_bool(false))),
144                facts: vec![],
145                operations: vec![],
146                proof: None,
147                rule_type: primitive_boolean().clone(),
148            },
149        );
150        let mut response = Response {
151            spec_name: "test_spec".to_string(),
152            facts: vec![],
153            results,
154        };
155
156        response.filter_rules(&["rule1".to_string()]);
157
158        assert_eq!(response.results.len(), 1);
159        assert_eq!(response.results.values().next().unwrap().rule.name, "rule1");
160    }
161
162    #[test]
163    fn test_rule_result_types() {
164        let success = RuleResult {
165            rule: dummy_evaluated_rule("rule1"),
166            result: OperationResult::Value(Box::new(LiteralValue::from_bool(true))),
167            facts: vec![],
168            operations: vec![],
169            proof: None,
170            rule_type: primitive_boolean().clone(),
171        };
172        assert!(matches!(success.result, OperationResult::Value(_)));
173
174        let missing = RuleResult {
175            rule: dummy_evaluated_rule("rule3"),
176            result: OperationResult::Veto(Some("Missing fact: fact1".to_string())),
177            facts: vec![crate::planning::semantics::Fact {
178                path: crate::planning::semantics::FactPath::new(vec![], "fact1".to_string()),
179                value: crate::planning::semantics::FactValue::Literal(
180                    crate::planning::semantics::LiteralValue::from_bool(false),
181                ),
182                source: None,
183            }],
184            operations: vec![],
185            proof: None,
186            rule_type: LemmaType::veto_type(),
187        };
188        assert_eq!(missing.facts.len(), 1);
189        assert_eq!(missing.facts[0].path.fact, "fact1");
190        assert!(matches!(missing.result, OperationResult::Veto(_)));
191
192        let veto = RuleResult {
193            rule: dummy_evaluated_rule("rule4"),
194            result: OperationResult::Veto(Some("Vetoed".to_string())),
195            facts: vec![],
196            operations: vec![],
197            proof: None,
198            rule_type: LemmaType::veto_type(),
199        };
200        assert_eq!(
201            veto.result,
202            OperationResult::Veto(Some("Vetoed".to_string()))
203        );
204    }
205}