Skip to main content

lemma/api/
value.rs

1//! ValueKind / LiteralValue / RuleResultValue JSON shapes.
2
3use crate::planning::semantics::{
4    LiteralValue as DomainLiteralValue, SemanticDateTime, SemanticTime,
5    ValueKind as DomainValueKind,
6};
7use crate::result_value::{
8    CalendarResult as DomainCalendarResult, RangeResult as DomainRangeResult,
9    RuleResultValue as DomainRuleResultValue,
10};
11use rust_decimal::Decimal;
12use serde::{Deserialize, Serialize};
13use std::collections::BTreeMap;
14
15/// Externally tagged value payload matching today's `ValueKind` Serialize shape.
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(rename_all = "lowercase")]
18pub enum ValueKind {
19    Number(Decimal),
20    Measure {
21        value: Decimal,
22    },
23    Ratio {
24        value: Decimal,
25    },
26    Text(String),
27    Date(SemanticDateTime),
28    Time(SemanticTime),
29    Boolean(bool),
30    Range {
31        from: Box<ValueKind>,
32        to: Box<ValueKind>,
33    },
34}
35
36impl From<&DomainValueKind> for ValueKind {
37    fn from(value: &DomainValueKind) -> Self {
38        match value {
39            DomainValueKind::Number(rational) => Self::Number(
40                rational
41                    .try_to_decimal()
42                    .expect("BUG: planned number must convert to decimal"),
43            ),
44            DomainValueKind::Measure(rational) => Self::Measure {
45                value: rational
46                    .try_to_decimal()
47                    .expect("BUG: planned measure must convert to decimal"),
48            },
49            DomainValueKind::Ratio(rational) => Self::Ratio {
50                value: rational
51                    .try_to_decimal()
52                    .expect("BUG: planned ratio must convert to decimal"),
53            },
54            DomainValueKind::Text(text) => Self::Text(text.clone()),
55            DomainValueKind::Date(date) => Self::Date(date.clone()),
56            DomainValueKind::Time(time) => Self::Time(time.clone()),
57            DomainValueKind::Boolean(flag) => Self::Boolean(*flag),
58            DomainValueKind::Range(from, to) => Self::Range {
59                from: Box::new(ValueKind::from(&from.value)),
60                to: Box::new(ValueKind::from(&to.value)),
61            },
62        }
63    }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct LiteralValue {
68    pub value: ValueKind,
69}
70
71impl From<&DomainLiteralValue> for LiteralValue {
72    fn from(literal: &DomainLiteralValue) -> Self {
73        Self {
74            value: ValueKind::from(&literal.value),
75        }
76    }
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
80pub struct CalendarResult {
81    pub value: Decimal,
82    pub unit: String,
83}
84
85impl From<&DomainCalendarResult> for CalendarResult {
86    fn from(calendar: &DomainCalendarResult) -> Self {
87        Self {
88            value: calendar.value,
89            unit: calendar.unit.clone(),
90        }
91    }
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
95pub struct RangeResult {
96    pub from: RuleResultValue,
97    pub to: RuleResultValue,
98}
99
100impl From<&DomainRangeResult> for RangeResult {
101    fn from(range: &DomainRangeResult) -> Self {
102        Self {
103            from: RuleResultValue::from(&range.from),
104            to: RuleResultValue::from(&range.to),
105        }
106    }
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
110pub struct RuleResultValue {
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub result: Option<String>,
113    #[serde(default, skip_serializing_if = "Option::is_none")]
114    pub measure: Option<BTreeMap<String, Decimal>>,
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub ratio: Option<BTreeMap<String, Decimal>>,
117    #[serde(default, skip_serializing_if = "Option::is_none")]
118    pub number: Option<Decimal>,
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub boolean: Option<bool>,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub text: Option<String>,
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub date: Option<SemanticDateTime>,
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub time: Option<SemanticTime>,
127    #[serde(default, skip_serializing_if = "Option::is_none")]
128    pub calendar: Option<CalendarResult>,
129    #[serde(default, skip_serializing_if = "Option::is_none")]
130    pub range: Option<Box<RangeResult>>,
131}
132
133impl From<&DomainRuleResultValue> for RuleResultValue {
134    fn from(value: &DomainRuleResultValue) -> Self {
135        Self {
136            result: value.result.clone(),
137            measure: value.measure.clone(),
138            ratio: value.ratio.clone(),
139            number: value.number,
140            boolean: value.boolean,
141            text: value.text.clone(),
142            date: value.date.clone(),
143            time: value.time.clone(),
144            calendar: value.calendar.as_ref().map(CalendarResult::from),
145            range: value
146                .range
147                .as_ref()
148                .map(|range| Box::new(RangeResult::from(range.as_ref()))),
149        }
150    }
151}