use crate::evaluation::operations::{OperationRecord, OperationResult, VetoType};
use crate::parsing::ast::DateTimeValue;
use crate::planning::semantics::{DataPath, Expression, LemmaType, RulePath, Source};
use indexmap::IndexMap;
use serde::Serialize;
use std::collections::BTreeSet;
#[derive(Debug, Clone, Serialize)]
pub struct EvaluatedRule {
pub name: String,
pub path: RulePath,
pub default_expression: Expression,
pub unless_branches: Vec<(Option<Expression>, Expression)>,
pub source_location: Source,
pub rule_type: LemmaType,
}
#[derive(Debug, Clone, Serialize)]
pub struct DataGroup {
pub data_path: String,
pub referencing_data_name: String,
pub data: Vec<crate::planning::semantics::Data>,
}
#[derive(Debug, Clone, Serialize)]
pub struct Response {
pub spec_name: String,
pub spec_hash: Option<String>,
pub spec_effective_from: Option<DateTimeValue>,
pub spec_effective_to: Option<DateTimeValue>,
pub data: Vec<DataGroup>,
pub results: IndexMap<String, RuleResult>,
}
#[derive(Debug, Clone, Serialize)]
pub struct RuleResult {
#[serde(skip_serializing)]
pub rule: EvaluatedRule,
pub result: OperationResult,
pub data: Vec<DataGroup>,
#[serde(skip_serializing)]
pub operations: Vec<OperationRecord>,
#[serde(skip_serializing_if = "Option::is_none")]
pub explanation: Option<crate::evaluation::explanation::Explanation>,
pub rule_type: LemmaType,
}
impl Response {
pub fn get(&self, rule_name: &str) -> Result<&RuleResult, crate::error::Error> {
self.results
.get(rule_name)
.ok_or_else(|| crate::error::Error::rule_not_found(rule_name, None::<String>))
}
pub fn add_result(&mut self, result: RuleResult) {
self.results.insert(result.rule.name.clone(), result);
}
pub fn filter_rules(&mut self, rule_names: &[String]) {
self.results.retain(|name, _| rule_names.contains(name));
}
#[must_use]
pub fn missing_data(&self) -> BTreeSet<DataPath> {
self.missing_data_ordered().into_iter().collect()
}
#[must_use]
pub fn missing_data_ordered(&self) -> Vec<DataPath> {
let mut seen = std::collections::HashSet::new();
let mut out = Vec::new();
for rr in self.results.values() {
if let OperationResult::Veto(VetoType::MissingData { data }) = &rr.result {
if seen.insert(data.clone()) {
out.push(data.clone());
}
}
}
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::planning::semantics::{
primitive_boolean, primitive_number, Expression, ExpressionKind, LemmaType, LiteralValue,
RulePath, Span,
};
use rust_decimal::Decimal;
use std::str::FromStr;
fn dummy_source() -> Source {
Source::new(
"test",
Span {
start: 0,
end: 0,
line: 1,
col: 1,
},
)
}
fn dummy_evaluated_rule(name: &str) -> EvaluatedRule {
EvaluatedRule {
name: name.to_string(),
path: RulePath::new(vec![], name.to_string()),
default_expression: Expression::new(
ExpressionKind::Literal(Box::new(LiteralValue::from_bool(true))),
dummy_source(),
),
unless_branches: vec![],
source_location: dummy_source(),
rule_type: primitive_number().clone(),
}
}
#[test]
fn test_response_serialization() {
let mut results = IndexMap::new();
results.insert(
"test_rule".to_string(),
RuleResult {
rule: dummy_evaluated_rule("test_rule"),
result: OperationResult::Value(Box::new(LiteralValue::number(
Decimal::from_str("42").unwrap(),
))),
data: vec![],
operations: vec![],
explanation: None,
rule_type: primitive_number().clone(),
},
);
let response = Response {
spec_name: "test_spec".to_string(),
spec_hash: None,
spec_effective_from: None,
spec_effective_to: None,
data: vec![],
results,
};
let json = serde_json::to_string(&response).unwrap();
assert!(json.contains("test_spec"));
assert!(json.contains("test_rule"));
assert!(json.contains("results"));
}
#[test]
fn test_response_filter_rules() {
let mut results = IndexMap::new();
results.insert(
"rule1".to_string(),
RuleResult {
rule: dummy_evaluated_rule("rule1"),
result: OperationResult::Value(Box::new(LiteralValue::from_bool(true))),
data: vec![],
operations: vec![],
explanation: None,
rule_type: primitive_boolean().clone(),
},
);
results.insert(
"rule2".to_string(),
RuleResult {
rule: dummy_evaluated_rule("rule2"),
result: OperationResult::Value(Box::new(LiteralValue::from_bool(false))),
data: vec![],
operations: vec![],
explanation: None,
rule_type: primitive_boolean().clone(),
},
);
let mut response = Response {
spec_name: "test_spec".to_string(),
spec_hash: None,
spec_effective_from: None,
spec_effective_to: None,
data: vec![],
results,
};
response.filter_rules(&["rule1".to_string()]);
assert_eq!(response.results.len(), 1);
assert_eq!(response.results.values().next().unwrap().rule.name, "rule1");
}
#[test]
fn test_rule_result_types() {
let success = RuleResult {
rule: dummy_evaluated_rule("rule1"),
result: OperationResult::Value(Box::new(LiteralValue::from_bool(true))),
data: vec![],
operations: vec![],
explanation: None,
rule_type: primitive_boolean().clone(),
};
assert!(matches!(success.result, OperationResult::Value(_)));
let missing = RuleResult {
rule: dummy_evaluated_rule("rule3"),
result: OperationResult::Veto(crate::evaluation::operations::VetoType::MissingData {
data: crate::planning::semantics::DataPath::new(vec![], "data1".to_string()),
}),
data: vec![DataGroup {
data_path: String::new(),
referencing_data_name: String::new(),
data: vec![crate::planning::semantics::Data {
path: crate::planning::semantics::DataPath::new(vec![], "data1".to_string()),
value: crate::planning::semantics::DataValue::Literal(
crate::planning::semantics::LiteralValue::from_bool(false),
),
source: None,
}],
}],
operations: vec![],
explanation: None,
rule_type: LemmaType::veto_type(),
};
assert_eq!(missing.data.len(), 1);
assert_eq!(missing.data[0].data[0].path.data, "data1");
assert!(matches!(missing.result, OperationResult::Veto(_)));
let veto = RuleResult {
rule: dummy_evaluated_rule("rule4"),
result: OperationResult::Veto(crate::evaluation::operations::VetoType::UserDefined {
message: Some("Vetoed".to_string()),
}),
data: vec![],
operations: vec![],
explanation: None,
rule_type: LemmaType::veto_type(),
};
assert_eq!(
veto.result,
OperationResult::Veto(crate::evaluation::operations::VetoType::UserDefined {
message: Some("Vetoed".to_string()),
})
);
}
}