use super::context::EvaluationContext;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ConditionResult {
True,
False,
Unknown,
}
impl ConditionResult {
pub fn is_true(self) -> bool {
matches!(self, ConditionResult::True)
}
pub fn is_false(self) -> bool {
matches!(self, ConditionResult::False)
}
pub fn is_unknown(self) -> bool {
matches!(self, ConditionResult::Unknown)
}
pub fn and(self, other: ConditionResult) -> ConditionResult {
match (self, other) {
(ConditionResult::False, _) | (_, ConditionResult::False) => ConditionResult::False,
(ConditionResult::True, ConditionResult::True) => ConditionResult::True,
_ => ConditionResult::Unknown,
}
}
pub fn or(self, other: ConditionResult) -> ConditionResult {
match (self, other) {
(ConditionResult::True, _) | (_, ConditionResult::True) => ConditionResult::True,
(ConditionResult::False, ConditionResult::False) => ConditionResult::False,
_ => ConditionResult::Unknown,
}
}
pub fn negate(self) -> ConditionResult {
match self {
ConditionResult::True => ConditionResult::False,
ConditionResult::False => ConditionResult::True,
ConditionResult::Unknown => ConditionResult::Unknown,
}
}
pub fn to_option(self) -> Option<bool> {
match self {
ConditionResult::True => Some(true),
ConditionResult::False => Some(false),
ConditionResult::Unknown => None,
}
}
}
impl From<bool> for ConditionResult {
fn from(value: bool) -> Self {
if value {
ConditionResult::True
} else {
ConditionResult::False
}
}
}
impl std::fmt::Display for ConditionResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ConditionResult::True => write!(f, "True"),
ConditionResult::False => write!(f, "False"),
ConditionResult::Unknown => write!(f, "Unknown"),
}
}
}
pub trait ConditionEvaluator: Send + Sync {
fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult;
fn is_external(&self, condition: u32) -> bool;
fn is_known(&self, _condition: u32) -> bool {
false
}
fn message_type(&self) -> &str;
fn format_version(&self) -> &str;
}
impl<T: ConditionEvaluator + ?Sized> ConditionEvaluator for std::sync::Arc<T> {
fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
(**self).evaluate(condition, ctx)
}
fn is_external(&self, condition: u32) -> bool {
(**self).is_external(condition)
}
fn is_known(&self, condition: u32) -> bool {
(**self).is_known(condition)
}
fn message_type(&self) -> &str {
(**self).message_type()
}
fn format_version(&self) -> &str {
(**self).format_version()
}
}
pub trait ExternalConditionProvider: Send + Sync {
fn evaluate(&self, condition_name: &str) -> ConditionResult;
}
pub struct NoOpExternalProvider;
impl ExternalConditionProvider for NoOpExternalProvider {
fn evaluate(&self, _condition_name: &str) -> ConditionResult {
ConditionResult::Unknown
}
}
pub fn presence_condition(message_type: &str) -> Option<u32> {
match message_type {
"UTILMD_Strom" | "UTILMD_Gas" => Some(166),
"INVOIC" => Some(22),
"ORDERS" => Some(12),
_ => None,
}
}
pub fn presence_conditions(message_type: &str) -> &'static [u32] {
match message_type {
"UTILMD_Strom" => &[166, 2003],
"UTILMD_Gas" => &[166],
"INVOIC" => &[22],
"ORDERS" => &[12],
_ => &[],
}
}
pub struct AbsentTarget<'a, E: ConditionEvaluator + ?Sized>(pub &'a E);
impl<E: ConditionEvaluator + ?Sized> ConditionEvaluator for AbsentTarget<'_, E> {
fn evaluate(&self, condition: u32, ctx: &EvaluationContext) -> ConditionResult {
if presence_conditions(self.0.message_type()).contains(&condition) {
return ConditionResult::False;
}
self.0.evaluate(condition, ctx)
}
fn is_external(&self, condition: u32) -> bool {
self.0.is_external(condition)
}
fn is_known(&self, condition: u32) -> bool {
self.0.is_known(condition)
}
fn message_type(&self) -> &str {
self.0.message_type()
}
fn format_version(&self) -> &str {
self.0.format_version()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct AlwaysTrue(&'static str);
impl ConditionEvaluator for AlwaysTrue {
fn evaluate(&self, _: u32, _: &EvaluationContext) -> ConditionResult {
ConditionResult::True
}
fn is_external(&self, _: u32) -> bool {
false
}
fn message_type(&self) -> &str {
self.0
}
fn format_version(&self) -> &str {
"FV2604"
}
}
#[test]
fn absent_target_answers_only_the_presence_condition_with_false() {
let external = NoOpExternalProvider;
let ctx = EvaluationContext::new("55043", &external, &[]);
let inner = AlwaysTrue("UTILMD_Strom");
let absent = AbsentTarget(&inner);
assert_eq!(absent.evaluate(166, &ctx), ConditionResult::False);
assert_eq!(absent.evaluate(674, &ctx), ConditionResult::True);
assert_eq!(absent.evaluate(2003, &ctx), ConditionResult::False);
let invoic = AlwaysTrue("INVOIC");
assert_eq!(
AbsentTarget(&invoic).evaluate(166, &ctx),
ConditionResult::True
);
assert_eq!(
AbsentTarget(&invoic).evaluate(22, &ctx),
ConditionResult::False
);
}
#[test]
fn test_condition_result_is_methods() {
assert!(ConditionResult::True.is_true());
assert!(!ConditionResult::True.is_false());
assert!(!ConditionResult::True.is_unknown());
assert!(!ConditionResult::False.is_true());
assert!(ConditionResult::False.is_false());
assert!(ConditionResult::Unknown.is_unknown());
}
#[test]
fn three_valued_and_or_not() {
use ConditionResult::{False as F, True as T, Unknown as U};
assert_eq!(T.and(T), T);
assert_eq!(T.and(U), U);
assert_eq!(U.and(F), F);
assert_eq!(F.or(F), F);
assert_eq!(F.or(U), U);
assert_eq!(U.or(T), T);
assert_eq!(U.negate(), U);
assert_eq!(T.negate(), F);
}
#[test]
fn test_condition_result_to_option() {
assert_eq!(ConditionResult::True.to_option(), Some(true));
assert_eq!(ConditionResult::False.to_option(), Some(false));
assert_eq!(ConditionResult::Unknown.to_option(), None);
}
#[test]
fn test_condition_result_from_bool() {
assert_eq!(ConditionResult::from(true), ConditionResult::True);
assert_eq!(ConditionResult::from(false), ConditionResult::False);
}
#[test]
fn test_condition_result_display() {
assert_eq!(format!("{}", ConditionResult::True), "True");
assert_eq!(format!("{}", ConditionResult::False), "False");
assert_eq!(format!("{}", ConditionResult::Unknown), "Unknown");
}
#[test]
fn test_noop_external_provider() {
let provider = NoOpExternalProvider;
assert_eq!(
provider.evaluate("MessageSplitting"),
ConditionResult::Unknown
);
assert_eq!(provider.evaluate("anything"), ConditionResult::Unknown);
}
}