1use crate::evaluation::operations::{OperationRecord, OperationResult};
2use crate::parsing::ast::DateTimeValue;
3use crate::planning::semantics::{Expression, Fact, LemmaType, RulePath, Source};
4use indexmap::IndexMap;
5use serde::Serialize;
6
7#[derive(Debug, Clone, Serialize)]
10pub struct EvaluatedRule {
11 pub name: String,
12 pub path: RulePath,
13 pub default_expression: Expression,
14 pub unless_branches: Vec<(Option<Expression>, Expression)>,
15 pub source_location: Source,
16 pub rule_type: LemmaType,
17}
18
19#[derive(Debug, Clone, Serialize)]
21pub struct Facts {
22 pub fact_path: String,
23 pub referencing_fact_name: String,
24 pub facts: Vec<Fact>,
25}
26
27#[derive(Debug, Clone, Serialize)]
29pub struct Response {
30 pub spec_name: String,
31 pub spec_hash: Option<String>,
32 pub spec_effective_from: Option<DateTimeValue>,
33 pub spec_effective_to: Option<DateTimeValue>,
34 pub facts: Vec<Facts>,
35 pub results: IndexMap<String, RuleResult>,
36}
37
38#[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 explanation: Option<crate::evaluation::explanation::Explanation>,
49 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 )
83 }
84
85 fn dummy_evaluated_rule(name: &str) -> EvaluatedRule {
86 EvaluatedRule {
87 name: name.to_string(),
88 path: RulePath::new(vec![], name.to_string()),
89 default_expression: Expression::new(
90 ExpressionKind::Literal(Box::new(LiteralValue::from_bool(true))),
91 dummy_source(),
92 ),
93 unless_branches: vec![],
94 source_location: dummy_source(),
95 rule_type: primitive_number().clone(),
96 }
97 }
98
99 #[test]
100 fn test_response_serialization() {
101 let mut results = IndexMap::new();
102 results.insert(
103 "test_rule".to_string(),
104 RuleResult {
105 rule: dummy_evaluated_rule("test_rule"),
106 result: OperationResult::Value(Box::new(LiteralValue::number(
107 Decimal::from_str("42").unwrap(),
108 ))),
109 facts: vec![],
110 operations: vec![],
111 explanation: None,
112 rule_type: primitive_number().clone(),
113 },
114 );
115 let response = Response {
116 spec_name: "test_spec".to_string(),
117 spec_hash: None,
118 spec_effective_from: None,
119 spec_effective_to: None,
120 facts: vec![],
121 results,
122 };
123
124 let json = serde_json::to_string(&response).unwrap();
125 assert!(json.contains("test_spec"));
126 assert!(json.contains("test_rule"));
127 assert!(json.contains("results"));
128 }
129
130 #[test]
131 fn test_response_filter_rules() {
132 let mut results = IndexMap::new();
133 results.insert(
134 "rule1".to_string(),
135 RuleResult {
136 rule: dummy_evaluated_rule("rule1"),
137 result: OperationResult::Value(Box::new(LiteralValue::from_bool(true))),
138 facts: vec![],
139 operations: vec![],
140 explanation: None,
141 rule_type: primitive_boolean().clone(),
142 },
143 );
144 results.insert(
145 "rule2".to_string(),
146 RuleResult {
147 rule: dummy_evaluated_rule("rule2"),
148 result: OperationResult::Value(Box::new(LiteralValue::from_bool(false))),
149 facts: vec![],
150 operations: vec![],
151 explanation: None,
152 rule_type: primitive_boolean().clone(),
153 },
154 );
155 let mut response = Response {
156 spec_name: "test_spec".to_string(),
157 spec_hash: None,
158 spec_effective_from: None,
159 spec_effective_to: None,
160 facts: vec![],
161 results,
162 };
163
164 response.filter_rules(&["rule1".to_string()]);
165
166 assert_eq!(response.results.len(), 1);
167 assert_eq!(response.results.values().next().unwrap().rule.name, "rule1");
168 }
169
170 #[test]
171 fn test_rule_result_types() {
172 let success = RuleResult {
173 rule: dummy_evaluated_rule("rule1"),
174 result: OperationResult::Value(Box::new(LiteralValue::from_bool(true))),
175 facts: vec![],
176 operations: vec![],
177 explanation: None,
178 rule_type: primitive_boolean().clone(),
179 };
180 assert!(matches!(success.result, OperationResult::Value(_)));
181
182 let missing = RuleResult {
183 rule: dummy_evaluated_rule("rule3"),
184 result: OperationResult::Veto(Some("Missing fact: fact1".to_string())),
185 facts: vec![crate::planning::semantics::Fact {
186 path: crate::planning::semantics::FactPath::new(vec![], "fact1".to_string()),
187 value: crate::planning::semantics::FactValue::Literal(
188 crate::planning::semantics::LiteralValue::from_bool(false),
189 ),
190 source: None,
191 }],
192 operations: vec![],
193 explanation: None,
194 rule_type: LemmaType::veto_type(),
195 };
196 assert_eq!(missing.facts.len(), 1);
197 assert_eq!(missing.facts[0].path.fact, "fact1");
198 assert!(matches!(missing.result, OperationResult::Veto(_)));
199
200 let veto = RuleResult {
201 rule: dummy_evaluated_rule("rule4"),
202 result: OperationResult::Veto(Some("Vetoed".to_string())),
203 facts: vec![],
204 operations: vec![],
205 explanation: None,
206 rule_type: LemmaType::veto_type(),
207 };
208 assert_eq!(
209 veto.result,
210 OperationResult::Veto(Some("Vetoed".to_string()))
211 );
212 }
213}