lemma-engine 0.9.9

A pure, declarative language for business rules.
Documentation
//! ValueKind / LiteralValue / RuleResultValue JSON shapes.

use crate::literals::rational_to_serialized_str;
use crate::planning::semantics::{
    LiteralValue as DomainLiteralValue, SemanticDateTime, SemanticTime,
    ValueKind as DomainValueKind,
};
use crate::result_value::{
    CalendarResult as DomainCalendarResult, RangeResult as DomainRangeResult,
    RuleResultValue as DomainRuleResultValue,
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

/// Externally tagged value payload matching today's `ValueKind` Serialize shape.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ValueKind {
    Number(String),
    Measure {
        value: String,
    },
    Ratio {
        value: String,
    },
    Text(String),
    Date(SemanticDateTime),
    Time(SemanticTime),
    Boolean(bool),
    Range {
        from: Box<ValueKind>,
        to: Box<ValueKind>,
    },
}

impl From<&DomainValueKind> for ValueKind {
    fn from(value: &DomainValueKind) -> Self {
        match value {
            DomainValueKind::Number(rational) => Self::Number(
                rational_to_serialized_str(rational)
                    .expect("BUG: planned bound must serialize to decimal string"),
            ),
            DomainValueKind::Measure(rational) => Self::Measure {
                value: rational_to_serialized_str(rational)
                    .expect("BUG: planned bound must serialize to decimal string"),
            },
            DomainValueKind::Ratio(rational) => Self::Ratio {
                value: rational_to_serialized_str(rational)
                    .expect("BUG: planned bound must serialize to decimal string"),
            },
            DomainValueKind::Text(text) => Self::Text(text.clone()),
            DomainValueKind::Date(date) => Self::Date(date.clone()),
            DomainValueKind::Time(time) => Self::Time(time.clone()),
            DomainValueKind::Boolean(flag) => Self::Boolean(*flag),
            DomainValueKind::Range(from, to) => Self::Range {
                from: Box::new(ValueKind::from(&from.value)),
                to: Box::new(ValueKind::from(&to.value)),
            },
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct LiteralValue {
    pub value: ValueKind,
}

impl From<&DomainLiteralValue> for LiteralValue {
    fn from(literal: &DomainLiteralValue) -> Self {
        Self {
            value: ValueKind::from(&literal.value),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CalendarResult {
    pub value: String,
    pub unit: String,
}

impl From<&DomainCalendarResult> for CalendarResult {
    fn from(calendar: &DomainCalendarResult) -> Self {
        Self {
            value: calendar.value.clone(),
            unit: calendar.unit.clone(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct RangeResult {
    pub from: RuleResultValue,
    pub to: RuleResultValue,
}

impl From<&DomainRangeResult> for RangeResult {
    fn from(range: &DomainRangeResult) -> Self {
        Self {
            from: RuleResultValue::from(&range.from),
            to: RuleResultValue::from(&range.to),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
pub struct RuleResultValue {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub display: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub measure: Option<BTreeMap<String, String>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub ratio: Option<BTreeMap<String, String>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub number: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub boolean: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub text: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub date: Option<SemanticDateTime>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub time: Option<SemanticTime>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub calendar: Option<CalendarResult>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub range: Option<Box<RangeResult>>,
}

impl From<&DomainRuleResultValue> for RuleResultValue {
    fn from(value: &DomainRuleResultValue) -> Self {
        Self {
            display: value.display.clone(),
            measure: value.measure.clone(),
            ratio: value.ratio.clone(),
            number: value.number.clone(),
            boolean: value.boolean,
            text: value.text.clone(),
            date: value.date.clone(),
            time: value.time.clone(),
            calendar: value.calendar.as_ref().map(CalendarResult::from),
            range: value
                .range
                .as_ref()
                .map(|range| Box::new(RangeResult::from(range.as_ref()))),
        }
    }
}