1use 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 transaction_group(message_type).map(|group| (2061, group))
227}
228
229pub fn transaction_group(message_type: &str) -> Option<&'static str> {
234 match message_type {
235 "UTILMD_Strom" | "UTILMD_Gas" => Some("SG4"),
236 _ => None,
237 }
238}
239
240pub fn binds_once(status: &str, once: u32) -> bool {
245 fn conjunct(expr: &crate::expr::ConditionExpr, once: u32) -> bool {
246 match expr {
247 crate::expr::ConditionExpr::Ref(id) => *id == once,
248 crate::expr::ConditionExpr::And(exprs) => exprs.iter().any(|e| conjunct(e, once)),
249 _ => false,
250 }
251 }
252 let lines: Vec<&str> = status
253 .lines()
254 .map(str::trim)
255 .filter(|l| !l.is_empty())
256 .collect();
257 !lines.is_empty()
258 && lines.iter().all(|line| {
259 matches!(
260 crate::expr::ConditionParser::parse(line),
261 Ok(Some(expr)) if conjunct(&expr, once)
262 )
263 })
264}
265
266pub struct AbsentTarget<'a, E: ConditionEvaluator + ?Sized>(pub &'a E);
273
274impl<E: ConditionEvaluator + ?Sized> ConditionEvaluator for AbsentTarget<'_, E> {
275 fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
276 if presence_conditions(self.0.message_type()).contains(&condition) {
277 return ConditionResult::False;
278 }
279 self.0.evaluate(condition, ctx)
280 }
281
282 fn is_external(&self, condition: u32) -> bool {
283 self.0.is_external(condition)
284 }
285
286 fn is_known(&self, condition: u32) -> bool {
287 self.0.is_known(condition)
288 }
289
290 fn message_type(&self) -> &str {
291 self.0.message_type()
292 }
293
294 fn format_version(&self) -> &str {
295 self.0.format_version()
296 }
297}
298
299pub struct PresentTarget<'a, E: ConditionEvaluator + ?Sized>(pub &'a E);
309
310impl<E: ConditionEvaluator + ?Sized> ConditionEvaluator for PresentTarget<'_, E> {
311 fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
312 if presence_conditions(self.0.message_type()).contains(&condition) {
313 return ConditionResult::True;
314 }
315 if !(1..500).contains(&condition) {
316 return ConditionResult::Unknown;
317 }
318 self.0.evaluate(condition, ctx)
319 }
320
321 fn is_external(&self, condition: u32) -> bool {
322 self.0.is_external(condition)
323 }
324
325 fn is_known(&self, condition: u32) -> bool {
326 self.0.is_known(condition)
327 }
328
329 fn message_type(&self) -> &str {
330 self.0.message_type()
331 }
332
333 fn format_version(&self) -> &str {
334 self.0.format_version()
335 }
336}
337
338pub fn refuses_presence<E: ConditionEvaluator + ?Sized>(
344 status: &str,
345 evaluator: &E,
346 ctx: &EvaluationContext,
347 ub_definitions: &std::collections::BTreeMap<String, crate::expr::ConditionExpr>,
348) -> bool {
349 refuses(status, &PresentTarget(evaluator), ctx, ub_definitions)
350}
351
352pub fn counted_repetitions(message_type: &str) -> &'static [u32] {
359 match message_type {
360 "ORDERS" => &[2005, 2006],
361 _ => &[],
362 }
363}
364
365pub fn repetition_limits(message_type: &str) -> &'static [(u32, usize, Option<usize>)] {
373 match message_type {
374 "UTILMD_Strom" => &[(2317, 2, None), (2318, 1, Some(1))],
375 _ => &[],
376 }
377}
378
379pub fn breaks_repetition_limit<E: ConditionEvaluator + ?Sized>(
382 status: &str,
383 evaluator: &E,
384 ctx: &EvaluationContext,
385 count: usize,
386) -> bool {
387 repetition_limits(evaluator.message_type())
388 .iter()
389 .filter(|(id, _, _)| status.contains(&format!("[{id}]")))
390 .any(|&(id, min, max)| {
391 evaluator.evaluate(id, ctx) == ConditionResult::True
392 && (count < min || max.is_some_and(|max| count > max))
393 })
394}
395
396struct CountTarget<'a, E: ConditionEvaluator + ?Sized>(&'a E);
399
400impl<E: ConditionEvaluator + ?Sized> ConditionEvaluator for CountTarget<'_, E> {
401 fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
402 if counted_repetitions(self.0.message_type()).contains(&condition) {
403 return self.0.evaluate(condition, ctx);
404 }
405 PresentTarget(self.0).evaluate(condition, ctx)
406 }
407
408 fn is_external(&self, condition: u32) -> bool {
409 self.0.is_external(condition)
410 }
411
412 fn is_known(&self, condition: u32) -> bool {
413 self.0.is_known(condition)
414 }
415
416 fn message_type(&self) -> &str {
417 self.0.message_type()
418 }
419
420 fn format_version(&self) -> &str {
421 self.0.format_version()
422 }
423}
424
425pub fn refuses_count<E: ConditionEvaluator + ?Sized>(
431 status: &str,
432 evaluator: &E,
433 ctx: &EvaluationContext,
434 ub_definitions: &std::collections::BTreeMap<String, crate::expr::ConditionExpr>,
435) -> bool {
436 let counted = counted_repetitions(evaluator.message_type());
437 let names_one = status.split('[').skip(1).any(|term| {
438 term.split(']')
439 .next()
440 .and_then(|id| id.parse::<u32>().ok())
441 .is_some_and(|id| counted.contains(&id))
442 });
443 names_one
444 && !refuses_presence(status, evaluator, ctx, ub_definitions)
445 && refuses(status, &CountTarget(evaluator), ctx, ub_definitions)
446}
447
448fn refuses<E: ConditionEvaluator>(
451 status: &str,
452 target: &E,
453 ctx: &EvaluationContext,
454 ub_definitions: &std::collections::BTreeMap<String, crate::expr::ConditionExpr>,
455) -> bool {
456 let status = status.trim();
457 let package = status.split('[').skip(1).any(|term| {
458 term.trim_start_matches(|c: char| c.is_ascii_digit())
459 .starts_with('P')
460 });
461 if status.contains('\n') || !status.contains('[') || package {
462 return false;
463 }
464 let keyword = &status[..status
465 .find(|c: char| !c.is_alphabetic())
466 .unwrap_or(status.len())];
467 if !matches!(keyword, "Muss" | "X") {
468 return false;
469 }
470 super::ConditionExprEvaluator::new(target).evaluate_status_with_ub(status, ctx, ub_definitions)
471 == ConditionResult::False
472}
473
474#[cfg(test)]
475mod tests {
476 use super::*;
477
478 struct AlwaysTrue(&'static str);
479
480 impl ConditionEvaluator for AlwaysTrue {
481 fn evaluate(&self, _: u32, _: &EvaluationContext) -> ConditionResult {
482 ConditionResult::True
483 }
484 fn is_external(&self, _: u32) -> bool {
485 false
486 }
487 fn message_type(&self) -> &str {
488 self.0
489 }
490 fn format_version(&self) -> &str {
491 "FV2604"
492 }
493 }
494
495 #[test]
496 fn once_per_transaction_binds_where_every_line_has_it() {
497 assert!(binds_once("Muss [483] ∧ [2061]", 2061));
498 assert!(binds_once("Muss [2061]", 2061));
499 assert!(binds_once(
500 "Muss ([77] ∧ [78]) ∧ [347] ∧ [2061]\r\nKann [2061]",
501 2061
502 ));
503 assert!(!binds_once("Soll [165] ∧ (([2061] ∧ [583]) ∨ [584])", 2061));
504 assert!(!binds_once(
505 "Muss ([32] ∧ [2061] ∧ [651]) ⊻ ([200] ∧ [601])",
506 2061
507 ));
508 assert!(!binds_once("Muss [483]", 2061));
509 assert!(!binds_once("", 2061));
510 }
511
512 #[test]
513 fn absent_target_answers_only_the_presence_condition_with_false() {
514 let external = NoOpExternalProvider;
515 let ctx = EvaluationContext::new("55043", &external, &[]);
516 let inner = AlwaysTrue("UTILMD_Strom");
517 let absent = AbsentTarget(&inner);
518 assert_eq!(absent.evaluate(166, &ctx), ConditionResult::False);
519 assert_eq!(absent.evaluate(674, &ctx), ConditionResult::True);
520 assert_eq!(absent.evaluate(2003, &ctx), ConditionResult::False);
522
523 let invoic = AlwaysTrue("INVOIC");
525 assert_eq!(
526 AbsentTarget(&invoic).evaluate(166, &ctx),
527 ConditionResult::True
528 );
529 assert_eq!(
530 AbsentTarget(&invoic).evaluate(22, &ctx),
531 ConditionResult::False
532 );
533 }
534
535 #[test]
536 fn test_condition_result_is_methods() {
537 assert!(ConditionResult::True.is_true());
538 assert!(!ConditionResult::True.is_false());
539 assert!(!ConditionResult::True.is_unknown());
540
541 assert!(!ConditionResult::False.is_true());
542 assert!(ConditionResult::False.is_false());
543
544 assert!(ConditionResult::Unknown.is_unknown());
545 }
546
547 #[test]
548 fn three_valued_and_or_not() {
549 use ConditionResult::{False as F, True as T, Unknown as U};
550 assert_eq!(T.and(T), T);
551 assert_eq!(T.and(U), U);
552 assert_eq!(U.and(F), F);
553 assert_eq!(F.or(F), F);
554 assert_eq!(F.or(U), U);
555 assert_eq!(U.or(T), T);
556 assert_eq!(U.negate(), U);
557 assert_eq!(T.negate(), F);
558 }
559
560 #[test]
561 fn test_condition_result_to_option() {
562 assert_eq!(ConditionResult::True.to_option(), Some(true));
563 assert_eq!(ConditionResult::False.to_option(), Some(false));
564 assert_eq!(ConditionResult::Unknown.to_option(), None);
565 }
566
567 #[test]
568 fn test_condition_result_from_bool() {
569 assert_eq!(ConditionResult::from(true), ConditionResult::True);
570 assert_eq!(ConditionResult::from(false), ConditionResult::False);
571 }
572
573 #[test]
574 fn test_condition_result_display() {
575 assert_eq!(format!("{}", ConditionResult::True), "True");
576 assert_eq!(format!("{}", ConditionResult::False), "False");
577 assert_eq!(format!("{}", ConditionResult::Unknown), "Unknown");
578 }
579
580 #[test]
581 fn test_noop_external_provider() {
582 let provider = NoOpExternalProvider;
583 assert_eq!(
584 provider.evaluate("MessageSplitting"),
585 ConditionResult::Unknown
586 );
587 assert_eq!(provider.evaluate("anything"), ConditionResult::Unknown);
588 }
589}