Skip to main content

automapper_validation/eval/
evaluator.rs

1//! Core condition evaluation traits.
2
3use super::context::EvaluationContext;
4
5/// Three-valued result of evaluating a single condition.
6///
7/// Unlike the C# implementation which uses `bool`, we use three-valued logic
8/// to support partial evaluation when external conditions are unavailable.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum ConditionResult {
11    /// The condition is satisfied.
12    True,
13    /// The condition is not satisfied.
14    False,
15    /// The condition cannot be determined (e.g., external condition without a provider).
16    Unknown,
17}
18
19impl ConditionResult {
20    /// Returns `true` if this is `ConditionResult::True`.
21    pub fn is_true(self) -> bool {
22        matches!(self, ConditionResult::True)
23    }
24
25    /// Returns `true` if this is `ConditionResult::False`.
26    pub fn is_false(self) -> bool {
27        matches!(self, ConditionResult::False)
28    }
29
30    /// Returns `true` if this is `ConditionResult::Unknown`.
31    pub fn is_unknown(self) -> bool {
32        matches!(self, ConditionResult::Unknown)
33    }
34
35    /// Converts to `Option<bool>`: True -> Some(true), False -> Some(false), Unknown -> None.
36    pub fn to_option(self) -> Option<bool> {
37        match self {
38            ConditionResult::True => Some(true),
39            ConditionResult::False => Some(false),
40            ConditionResult::Unknown => None,
41        }
42    }
43}
44
45impl From<bool> for ConditionResult {
46    fn from(value: bool) -> Self {
47        if value {
48            ConditionResult::True
49        } else {
50            ConditionResult::False
51        }
52    }
53}
54
55impl std::fmt::Display for ConditionResult {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        match self {
58            ConditionResult::True => write!(f, "True"),
59            ConditionResult::False => write!(f, "False"),
60            ConditionResult::Unknown => write!(f, "Unknown"),
61        }
62    }
63}
64
65/// Evaluates individual AHB conditions by number.
66///
67/// Implementations are typically generated from AHB XML schemas (one per
68/// message type and format version). Each condition number maps to a
69/// specific business rule check.
70pub trait ConditionEvaluator: Send + Sync {
71    /// Evaluate a single condition by number.
72    ///
73    /// Returns `ConditionResult::Unknown` for unrecognized condition numbers
74    /// or conditions that require unavailable external context.
75    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult;
76
77    /// Returns `true` if the given condition requires external context
78    /// (i.e., cannot be determined from the EDIFACT message alone).
79    fn is_external(&self, condition: u32) -> bool;
80
81    /// Returns `true` if this evaluator has an implementation for the given
82    /// condition number (whether internal or external). Conditions that fall
83    /// through to the `_ => Unknown` wildcard return `false`.
84    ///
85    /// This allows distinguishing "implemented but returned Unknown because
86    /// the relevant data isn't present in the message" from "not implemented
87    /// at all".
88    fn is_known(&self, _condition: u32) -> bool {
89        false
90    }
91
92    /// Returns the message type this evaluator handles (e.g., "UTILMD").
93    fn message_type(&self) -> &str;
94
95    /// Returns the format version this evaluator handles (e.g., "FV2510").
96    fn format_version(&self) -> &str;
97}
98
99impl<T: ConditionEvaluator + ?Sized> ConditionEvaluator for std::sync::Arc<T> {
100    fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
101        (**self).evaluate(condition, ctx)
102    }
103
104    fn is_external(&self, condition: u32) -> bool {
105        (**self).is_external(condition)
106    }
107
108    fn is_known(&self, condition: u32) -> bool {
109        (**self).is_known(condition)
110    }
111
112    fn message_type(&self) -> &str {
113        (**self).message_type()
114    }
115
116    fn format_version(&self) -> &str {
117        (**self).format_version()
118    }
119}
120
121/// Provider for external conditions that depend on context outside the EDIFACT message.
122///
123/// External conditions are things like:
124/// - [1] "Wenn Aufteilung vorhanden" (message splitting status)
125/// - [14] "Wenn Datum bekannt" (whether a date is known)
126/// - [30] "Wenn Antwort auf Aktivierung" (response to activation)
127///
128/// These cannot be determined from the EDIFACT content alone and require
129/// business context from the calling system.
130pub trait ExternalConditionProvider: Send + Sync {
131    /// Evaluate an external condition by name.
132    ///
133    /// The `condition_name` corresponds to the speaking name from the
134    /// generated external conditions constants (e.g., "MessageSplitting",
135    /// "DateKnown").
136    fn evaluate(&self, condition_name: &str) -> ConditionResult;
137}
138
139/// A no-op external condition provider that returns `Unknown` for everything.
140///
141/// Useful when no external context is available — conditions will propagate
142/// as `Unknown` through the expression evaluator.
143pub struct NoOpExternalProvider;
144
145impl ExternalConditionProvider for NoOpExternalProvider {
146    fn evaluate(&self, _condition_name: &str) -> ConditionResult {
147        ConditionResult::Unknown
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn test_condition_result_is_methods() {
157        assert!(ConditionResult::True.is_true());
158        assert!(!ConditionResult::True.is_false());
159        assert!(!ConditionResult::True.is_unknown());
160
161        assert!(!ConditionResult::False.is_true());
162        assert!(ConditionResult::False.is_false());
163
164        assert!(ConditionResult::Unknown.is_unknown());
165    }
166
167    #[test]
168    fn test_condition_result_to_option() {
169        assert_eq!(ConditionResult::True.to_option(), Some(true));
170        assert_eq!(ConditionResult::False.to_option(), Some(false));
171        assert_eq!(ConditionResult::Unknown.to_option(), None);
172    }
173
174    #[test]
175    fn test_condition_result_from_bool() {
176        assert_eq!(ConditionResult::from(true), ConditionResult::True);
177        assert_eq!(ConditionResult::from(false), ConditionResult::False);
178    }
179
180    #[test]
181    fn test_condition_result_display() {
182        assert_eq!(format!("{}", ConditionResult::True), "True");
183        assert_eq!(format!("{}", ConditionResult::False), "False");
184        assert_eq!(format!("{}", ConditionResult::Unknown), "Unknown");
185    }
186
187    #[test]
188    fn test_noop_external_provider() {
189        let provider = NoOpExternalProvider;
190        assert_eq!(
191            provider.evaluate("MessageSplitting"),
192            ConditionResult::Unknown
193        );
194        assert_eq!(provider.evaluate("anything"), ConditionResult::Unknown);
195    }
196}