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
104 fn dummy_source() -> Source {
105 Source::new(
106 crate::parsing::source::SourceType::Volatile,
107 Span {
108 start: 0,
109 end: 0,
110 line: 1,
111 col: 1,
112 },
113 )
114 }
115
116 fn dummy_evaluated_rule(name: &str) -> EvaluatedRule {
117 EvaluatedRule {
118 name: name.to_string(),
119 path: RulePath::new(vec![], name.to_string()),
120 default_expression: Expression::new(
121 ExpressionKind::Literal(Box::new(LiteralValue::from_bool(true))),
122 dummy_source(),
123 ),
124 unless_branches: vec![],
125 source_location: dummy_source(),
126 rule_type: primitive_number().clone(),
127 }
128 }
129
130 #[test]
131 fn test_response_serialization() {
132 let mut results = IndexMap::new();
133 results.insert(
134 "test_rule".to_string(),
135 RuleResult {
136 rule: dummy_evaluated_rule("test_rule"),
137 result: OperationResult::Value(Box::new(LiteralValue::number_from_decimal(
138 Decimal::from(42),
139 ))),
140 data: vec![],
141 operations: vec![],
142 explanation: None,
143 rule_type: primitive_number().clone(),
144 },
145 );
146 let response = Response {
147 spec_name: "test_spec".to_string(),
148 spec_hash: None,
149 spec_effective_from: None,
150 spec_effective_to: None,
151 data: vec![],
152 results,
153 };
154
155 let json = serde_json::to_string(&response).unwrap();
156 assert!(json.contains("test_spec"));
157 assert!(json.contains("test_rule"));
158 assert!(json.contains("results"));
159 }
160
161 #[test]
162 fn test_response_filter_rules() {
163 let mut results = IndexMap::new();
164 results.insert(
165 "rule1".to_string(),
166 RuleResult {
167 rule: dummy_evaluated_rule("rule1"),
168 result: OperationResult::Value(Box::new(LiteralValue::from_bool(true))),
169 data: vec![],
170 operations: vec![],
171 explanation: None,
172 rule_type: primitive_boolean().clone(),
173 },
174 );
175 results.insert(
176 "rule2".to_string(),
177 RuleResult {
178 rule: dummy_evaluated_rule("rule2"),
179 result: OperationResult::Value(Box::new(LiteralValue::from_bool(false))),
180 data: vec![],
181 operations: vec![],
182 explanation: None,
183 rule_type: primitive_boolean().clone(),
184 },
185 );
186 let mut response = Response {
187 spec_name: "test_spec".to_string(),
188 spec_hash: None,
189 spec_effective_from: None,
190 spec_effective_to: None,
191 data: vec![],
192 results,
193 };
194
195 response.filter_rules(&["rule1".to_string()]);
196
197 assert_eq!(response.results.len(), 1);
198 assert_eq!(response.results.values().next().unwrap().rule.name, "rule1");
199 }
200
201 #[test]
202 fn test_rule_result_types() {
203 let success = RuleResult {
204 rule: dummy_evaluated_rule("rule1"),
205 result: OperationResult::Value(Box::new(LiteralValue::from_bool(true))),
206 data: vec![],
207 operations: vec![],
208 explanation: None,
209 rule_type: primitive_boolean().clone(),
210 };
211 assert!(matches!(success.result, OperationResult::Value(_)));
212
213 let missing = RuleResult {
214 rule: dummy_evaluated_rule("rule3"),
215 result: OperationResult::Veto(crate::evaluation::operations::VetoType::MissingData {
216 data: crate::planning::semantics::DataPath::new(vec![], "data1".to_string()),
217 }),
218 data: vec![DataGroup {
219 data_path: String::new(),
220 referencing_data_name: String::new(),
221 data: vec![crate::planning::semantics::Data {
222 path: crate::planning::semantics::DataPath::new(vec![], "data1".to_string()),
223 value: crate::planning::semantics::DataValue::from_bound_literal(
224 crate::planning::semantics::LiteralValue::from_bool(false),
225 ),
226 source: None,
227 }],
228 }],
229 operations: vec![],
230 explanation: None,
231 rule_type: LemmaType::veto_type(),
232 };
233 assert_eq!(missing.data.len(), 1);
234 assert_eq!(missing.data[0].data[0].path.data, "data1");
235 assert!(matches!(missing.result, OperationResult::Veto(_)));
236
237 let veto = RuleResult {
238 rule: dummy_evaluated_rule("rule4"),
239 result: OperationResult::Veto(crate::evaluation::operations::VetoType::UserDefined {
240 message: Some("Vetoed".to_string()),
241 }),
242 data: vec![],
243 operations: vec![],
244 explanation: None,
245 rule_type: LemmaType::veto_type(),
246 };
247 assert_eq!(
248 veto.result,
249 OperationResult::Veto(crate::evaluation::operations::VetoType::UserDefined {
250 message: Some("Vetoed".to_string()),
251 })
252 );
253 }
254}