automapper_validation/eval/
evaluator.rs1use super::context::EvaluationContext;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum ConditionResult {
11 True,
13 False,
15 Unknown,
17}
18
19impl ConditionResult {
20 pub fn is_true(self) -> bool {
22 matches!(self, ConditionResult::True)
23 }
24
25 pub fn is_false(self) -> bool {
27 matches!(self, ConditionResult::False)
28 }
29
30 pub fn is_unknown(self) -> bool {
32 matches!(self, ConditionResult::Unknown)
33 }
34
35 pub fn and(self, other: ConditionResult) -> ConditionResult {
37 match (self, other) {
38 (ConditionResult::False, _) | (_, ConditionResult::False) => ConditionResult::False,
39 (ConditionResult::True, ConditionResult::True) => ConditionResult::True,
40 _ => ConditionResult::Unknown,
41 }
42 }
43
44 pub fn or(self, other: ConditionResult) -> ConditionResult {
46 match (self, other) {
47 (ConditionResult::True, _) | (_, ConditionResult::True) => ConditionResult::True,
48 (ConditionResult::False, ConditionResult::False) => ConditionResult::False,
49 _ => ConditionResult::Unknown,
50 }
51 }
52
53 pub fn negate(self) -> ConditionResult {
55 match self {
56 ConditionResult::True => ConditionResult::False,
57 ConditionResult::False => ConditionResult::True,
58 ConditionResult::Unknown => ConditionResult::Unknown,
59 }
60 }
61
62 pub fn to_option(self) -> Option<bool> {
64 match self {
65 ConditionResult::True => Some(true),
66 ConditionResult::False => Some(false),
67 ConditionResult::Unknown => None,
68 }
69 }
70}
71
72impl From<bool> for ConditionResult {
73 fn from(value: bool) -> Self {
74 if value {
75 ConditionResult::True
76 } else {
77 ConditionResult::False
78 }
79 }
80}
81
82impl std::fmt::Display for ConditionResult {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 match self {
85 ConditionResult::True => write!(f, "True"),
86 ConditionResult::False => write!(f, "False"),
87 ConditionResult::Unknown => write!(f, "Unknown"),
88 }
89 }
90}
91
92pub trait ConditionEvaluator: Send + Sync {
98 fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult;
103
104 fn is_external(&self, condition: u32) -> bool;
107
108 fn is_known(&self, _condition: u32) -> bool {
116 false
117 }
118
119 fn message_type(&self) -> &str;
121
122 fn format_version(&self) -> &str;
124}
125
126impl<T: ConditionEvaluator + ?Sized> ConditionEvaluator for std::sync::Arc<T> {
127 fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
128 (**self).evaluate(condition, ctx)
129 }
130
131 fn is_external(&self, condition: u32) -> bool {
132 (**self).is_external(condition)
133 }
134
135 fn is_known(&self, condition: u32) -> bool {
136 (**self).is_known(condition)
137 }
138
139 fn message_type(&self) -> &str {
140 (**self).message_type()
141 }
142
143 fn format_version(&self) -> &str {
144 (**self).format_version()
145 }
146}
147
148pub trait ExternalConditionProvider: Send + Sync {
158 fn evaluate(&self, condition_name: &str) -> ConditionResult;
164}
165
166pub struct NoOpExternalProvider;
171
172impl ExternalConditionProvider for NoOpExternalProvider {
173 fn evaluate(&self, _condition_name: &str) -> ConditionResult {
174 ConditionResult::Unknown
175 }
176}
177
178pub fn presence_condition(message_type: &str) -> Option<u32> {
191 match message_type {
192 "UTILMD_Strom" | "UTILMD_Gas" => Some(166),
193 "INVOIC" => Some(22),
194 "ORDERS" => Some(12),
195 _ => None,
196 }
197}
198
199pub fn presence_conditions(message_type: &str) -> &'static [u32] {
210 match message_type {
211 "UTILMD_Strom" => &[166, 2003],
212 "UTILMD_Gas" => &[166],
213 "INVOIC" => &[22],
214 "ORDERS" => &[12],
215 _ => &[],
216 }
217}
218
219pub fn once_per_transaction(message_type: &str) -> Option<(u32, &'static str)> {
226 match message_type {
227 "UTILMD_Strom" | "UTILMD_Gas" => Some((2061, "SG4")),
228 _ => None,
229 }
230}
231
232pub fn binds_once(status: &str, once: u32) -> bool {
237 fn conjunct(expr: &crate::expr::ConditionExpr, once: u32) -> bool {
238 match expr {
239 crate::expr::ConditionExpr::Ref(id) => *id == once,
240 crate::expr::ConditionExpr::And(exprs) => exprs.iter().any(|e| conjunct(e, once)),
241 _ => false,
242 }
243 }
244 let lines: Vec<&str> = status
245 .lines()
246 .map(str::trim)
247 .filter(|l| !l.is_empty())
248 .collect();
249 !lines.is_empty()
250 && lines.iter().all(|line| {
251 matches!(
252 crate::expr::ConditionParser::parse(line),
253 Ok(Some(expr)) if conjunct(&expr, once)
254 )
255 })
256}
257
258pub struct AbsentTarget<'a, E: ConditionEvaluator + ?Sized>(pub &'a E);
265
266impl<E: ConditionEvaluator + ?Sized> ConditionEvaluator for AbsentTarget<'_, E> {
267 fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
268 if presence_conditions(self.0.message_type()).contains(&condition) {
269 return ConditionResult::False;
270 }
271 self.0.evaluate(condition, ctx)
272 }
273
274 fn is_external(&self, condition: u32) -> bool {
275 self.0.is_external(condition)
276 }
277
278 fn is_known(&self, condition: u32) -> bool {
279 self.0.is_known(condition)
280 }
281
282 fn message_type(&self) -> &str {
283 self.0.message_type()
284 }
285
286 fn format_version(&self) -> &str {
287 self.0.format_version()
288 }
289}
290
291pub struct PresentTarget<'a, E: ConditionEvaluator + ?Sized>(pub &'a E);
301
302impl<E: ConditionEvaluator + ?Sized> ConditionEvaluator for PresentTarget<'_, E> {
303 fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
304 if presence_conditions(self.0.message_type()).contains(&condition) {
305 return ConditionResult::True;
306 }
307 if !(1..500).contains(&condition) {
308 return ConditionResult::Unknown;
309 }
310 self.0.evaluate(condition, ctx)
311 }
312
313 fn is_external(&self, condition: u32) -> bool {
314 self.0.is_external(condition)
315 }
316
317 fn is_known(&self, condition: u32) -> bool {
318 self.0.is_known(condition)
319 }
320
321 fn message_type(&self) -> &str {
322 self.0.message_type()
323 }
324
325 fn format_version(&self) -> &str {
326 self.0.format_version()
327 }
328}
329
330pub fn refuses_presence<E: ConditionEvaluator + ?Sized>(
336 status: &str,
337 evaluator: &E,
338 ctx: &EvaluationContext,
339 ub_definitions: &std::collections::BTreeMap<String, crate::expr::ConditionExpr>,
340) -> bool {
341 let status = status.trim();
342 let package = status.split('[').skip(1).any(|term| {
343 term.trim_start_matches(|c: char| c.is_ascii_digit())
344 .starts_with('P')
345 });
346 if status.contains('\n') || !status.contains('[') || package {
347 return false;
348 }
349 let keyword = &status[..status
350 .find(|c: char| !c.is_alphabetic())
351 .unwrap_or(status.len())];
352 if !matches!(keyword, "Muss" | "X") {
353 return false;
354 }
355 let present = PresentTarget(evaluator);
356 super::ConditionExprEvaluator::new(&present).evaluate_status_with_ub(
357 status,
358 ctx,
359 ub_definitions,
360 ) == ConditionResult::False
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366
367 struct AlwaysTrue(&'static str);
368
369 impl ConditionEvaluator for AlwaysTrue {
370 fn evaluate(&self, _: u32, _: &EvaluationContext) -> ConditionResult {
371 ConditionResult::True
372 }
373 fn is_external(&self, _: u32) -> bool {
374 false
375 }
376 fn message_type(&self) -> &str {
377 self.0
378 }
379 fn format_version(&self) -> &str {
380 "FV2604"
381 }
382 }
383
384 #[test]
385 fn once_per_transaction_binds_where_every_line_has_it() {
386 assert!(binds_once("Muss [483] ∧ [2061]", 2061));
387 assert!(binds_once("Muss [2061]", 2061));
388 assert!(binds_once(
389 "Muss ([77] ∧ [78]) ∧ [347] ∧ [2061]\r\nKann [2061]",
390 2061
391 ));
392 assert!(!binds_once("Soll [165] ∧ (([2061] ∧ [583]) ∨ [584])", 2061));
393 assert!(!binds_once(
394 "Muss ([32] ∧ [2061] ∧ [651]) ⊻ ([200] ∧ [601])",
395 2061
396 ));
397 assert!(!binds_once("Muss [483]", 2061));
398 assert!(!binds_once("", 2061));
399 }
400
401 #[test]
402 fn absent_target_answers_only_the_presence_condition_with_false() {
403 let external = NoOpExternalProvider;
404 let ctx = EvaluationContext::new("55043", &external, &[]);
405 let inner = AlwaysTrue("UTILMD_Strom");
406 let absent = AbsentTarget(&inner);
407 assert_eq!(absent.evaluate(166, &ctx), ConditionResult::False);
408 assert_eq!(absent.evaluate(674, &ctx), ConditionResult::True);
409 assert_eq!(absent.evaluate(2003, &ctx), ConditionResult::False);
411
412 let invoic = AlwaysTrue("INVOIC");
414 assert_eq!(
415 AbsentTarget(&invoic).evaluate(166, &ctx),
416 ConditionResult::True
417 );
418 assert_eq!(
419 AbsentTarget(&invoic).evaluate(22, &ctx),
420 ConditionResult::False
421 );
422 }
423
424 #[test]
425 fn test_condition_result_is_methods() {
426 assert!(ConditionResult::True.is_true());
427 assert!(!ConditionResult::True.is_false());
428 assert!(!ConditionResult::True.is_unknown());
429
430 assert!(!ConditionResult::False.is_true());
431 assert!(ConditionResult::False.is_false());
432
433 assert!(ConditionResult::Unknown.is_unknown());
434 }
435
436 #[test]
437 fn three_valued_and_or_not() {
438 use ConditionResult::{False as F, True as T, Unknown as U};
439 assert_eq!(T.and(T), T);
440 assert_eq!(T.and(U), U);
441 assert_eq!(U.and(F), F);
442 assert_eq!(F.or(F), F);
443 assert_eq!(F.or(U), U);
444 assert_eq!(U.or(T), T);
445 assert_eq!(U.negate(), U);
446 assert_eq!(T.negate(), F);
447 }
448
449 #[test]
450 fn test_condition_result_to_option() {
451 assert_eq!(ConditionResult::True.to_option(), Some(true));
452 assert_eq!(ConditionResult::False.to_option(), Some(false));
453 assert_eq!(ConditionResult::Unknown.to_option(), None);
454 }
455
456 #[test]
457 fn test_condition_result_from_bool() {
458 assert_eq!(ConditionResult::from(true), ConditionResult::True);
459 assert_eq!(ConditionResult::from(false), ConditionResult::False);
460 }
461
462 #[test]
463 fn test_condition_result_display() {
464 assert_eq!(format!("{}", ConditionResult::True), "True");
465 assert_eq!(format!("{}", ConditionResult::False), "False");
466 assert_eq!(format!("{}", ConditionResult::Unknown), "Unknown");
467 }
468
469 #[test]
470 fn test_noop_external_provider() {
471 let provider = NoOpExternalProvider;
472 assert_eq!(
473 provider.evaluate("MessageSplitting"),
474 ConditionResult::Unknown
475 );
476 assert_eq!(provider.evaluate("anything"), ConditionResult::Unknown);
477 }
478}