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