1use crate::evaluation::operations::{OperationRecord, OperationResult, VetoType};
2use crate::parsing::ast::DateTimeValue;
3use crate::planning::semantics::{DataPath, Expression, LemmaType, RulePath, Source};
4use indexmap::IndexMap;
5use serde::Serialize;
6use std::collections::BTreeSet;
7
8#[derive(Debug, Clone, Serialize)]
11pub struct EvaluatedRule {
12 pub name: String,
13 pub path: RulePath,
14 pub default_expression: Expression,
15 pub unless_branches: Vec<(Option<Expression>, Expression)>,
16 pub source_location: Source,
17 pub rule_type: LemmaType,
18}
19
20#[derive(Debug, Clone, Serialize)]
22pub struct DataGroup {
23 pub data_path: String,
24 pub referencing_data_name: String,
25 pub data: Vec<crate::planning::semantics::Data>,
26}
27
28#[derive(Debug, Clone, Serialize)]
30pub struct Response {
31 pub spec_name: String,
32 pub spec_hash: Option<String>,
33 pub spec_effective_from: Option<DateTimeValue>,
34 pub spec_effective_to: Option<DateTimeValue>,
35 pub data: Vec<DataGroup>,
36 pub results: IndexMap<String, RuleResult>,
37}
38
39#[derive(Debug, Clone, Serialize)]
41pub struct RuleResult {
42 #[serde(skip_serializing)]
43 pub rule: EvaluatedRule,
44 pub result: OperationResult,
45 pub data: Vec<DataGroup>,
46 #[serde(skip_serializing)]
47 pub operations: Vec<OperationRecord>,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub explanation: Option<crate::evaluation::explanation::Explanation>,
50 pub rule_type: LemmaType,
52}
53
54impl Response {
55 pub fn get(&self, rule_name: &str) -> Result<&RuleResult, crate::error::Error> {
59 self.results
60 .get(rule_name)
61 .ok_or_else(|| crate::error::Error::rule_not_found(rule_name, None::<String>))
62 }
63
64 pub fn add_result(&mut self, result: RuleResult) {
65 self.results.insert(result.rule.name.clone(), result);
66 }
67
68 pub fn filter_rules(&mut self, rule_names: &[String]) {
69 self.results.retain(|name, _| rule_names.contains(name));
70 }
71
72 #[must_use]
74 pub fn missing_data(&self) -> BTreeSet<DataPath> {
75 self.missing_data_ordered().into_iter().collect()
76 }
77
78 #[must_use]
81 pub fn missing_data_ordered(&self) -> Vec<DataPath> {
82 let mut seen = std::collections::HashSet::new();
83 let mut out = Vec::new();
84 for rr in self.results.values() {
85 if let OperationResult::Veto(VetoType::MissingData { data }) = &rr.result {
86 if seen.insert(data.clone()) {
87 out.push(data.clone());
88 }
89 }
90 }
91 out
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use crate::planning::semantics::{
99 primitive_boolean, primitive_number, Expression, ExpressionKind, LemmaType, LiteralValue,
100 RulePath, Span,
101 };
102 use rust_decimal::Decimal;
103 use std::str::FromStr;
104
105 fn dummy_source() -> Source {
106 Source::new(
107 "test",
108 Span {
109 start: 0,
110 end: 0,
111 line: 1,
112 col: 1,
113 },
114 )
115 }
116
117 fn dummy_evaluated_rule(name: &str) -> EvaluatedRule {
118 EvaluatedRule {
119 name: name.to_string(),
120 path: RulePath::new(vec![], name.to_string()),
121 default_expression: Expression::new(
122 ExpressionKind::Literal(Box::new(LiteralValue::from_bool(true))),
123 dummy_source(),
124 ),
125 unless_branches: vec![],
126 source_location: dummy_source(),
127 rule_type: primitive_number().clone(),
128 }
129 }
130
131 #[test]
132 fn test_response_serialization() {
133 let mut results = IndexMap::new();
134 results.insert(
135 "test_rule".to_string(),
136 RuleResult {
137 rule: dummy_evaluated_rule("test_rule"),
138 result: OperationResult::Value(Box::new(LiteralValue::number(
139 Decimal::from_str("42").unwrap(),
140 ))),
141 data: vec![],
142 operations: vec![],
143 explanation: None,
144 rule_type: primitive_number().clone(),
145 },
146 );
147 let response = Response {
148 spec_name: "test_spec".to_string(),
149 spec_hash: None,
150 spec_effective_from: None,
151 spec_effective_to: None,
152 data: vec![],
153 results,
154 };
155
156 let json = serde_json::to_string(&response).unwrap();
157 assert!(json.contains("test_spec"));
158 assert!(json.contains("test_rule"));
159 assert!(json.contains("results"));
160 }
161
162 #[test]
163 fn test_response_filter_rules() {
164 let mut results = IndexMap::new();
165 results.insert(
166 "rule1".to_string(),
167 RuleResult {
168 rule: dummy_evaluated_rule("rule1"),
169 result: OperationResult::Value(Box::new(LiteralValue::from_bool(true))),
170 data: vec![],
171 operations: vec![],
172 explanation: None,
173 rule_type: primitive_boolean().clone(),
174 },
175 );
176 results.insert(
177 "rule2".to_string(),
178 RuleResult {
179 rule: dummy_evaluated_rule("rule2"),
180 result: OperationResult::Value(Box::new(LiteralValue::from_bool(false))),
181 data: vec![],
182 operations: vec![],
183 explanation: None,
184 rule_type: primitive_boolean().clone(),
185 },
186 );
187 let mut response = Response {
188 spec_name: "test_spec".to_string(),
189 spec_hash: None,
190 spec_effective_from: None,
191 spec_effective_to: None,
192 data: vec![],
193 results,
194 };
195
196 response.filter_rules(&["rule1".to_string()]);
197
198 assert_eq!(response.results.len(), 1);
199 assert_eq!(response.results.values().next().unwrap().rule.name, "rule1");
200 }
201
202 #[test]
203 fn test_rule_result_types() {
204 let success = RuleResult {
205 rule: dummy_evaluated_rule("rule1"),
206 result: OperationResult::Value(Box::new(LiteralValue::from_bool(true))),
207 data: vec![],
208 operations: vec![],
209 explanation: None,
210 rule_type: primitive_boolean().clone(),
211 };
212 assert!(matches!(success.result, OperationResult::Value(_)));
213
214 let missing = RuleResult {
215 rule: dummy_evaluated_rule("rule3"),
216 result: OperationResult::Veto(crate::evaluation::operations::VetoType::MissingData {
217 data: crate::planning::semantics::DataPath::new(vec![], "data1".to_string()),
218 }),
219 data: vec![DataGroup {
220 data_path: String::new(),
221 referencing_data_name: String::new(),
222 data: vec![crate::planning::semantics::Data {
223 path: crate::planning::semantics::DataPath::new(vec![], "data1".to_string()),
224 value: crate::planning::semantics::DataValue::Literal(
225 crate::planning::semantics::LiteralValue::from_bool(false),
226 ),
227 source: None,
228 }],
229 }],
230 operations: vec![],
231 explanation: None,
232 rule_type: LemmaType::veto_type(),
233 };
234 assert_eq!(missing.data.len(), 1);
235 assert_eq!(missing.data[0].data[0].path.data, "data1");
236 assert!(matches!(missing.result, OperationResult::Veto(_)));
237
238 let veto = RuleResult {
239 rule: dummy_evaluated_rule("rule4"),
240 result: OperationResult::Veto(crate::evaluation::operations::VetoType::UserDefined {
241 message: Some("Vetoed".to_string()),
242 }),
243 data: vec![],
244 operations: vec![],
245 explanation: None,
246 rule_type: LemmaType::veto_type(),
247 };
248 assert_eq!(
249 veto.result,
250 OperationResult::Veto(crate::evaluation::operations::VetoType::UserDefined {
251 message: Some("Vetoed".to_string()),
252 })
253 );
254 }
255}