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 indexmap::IndexMap;
12use rust_decimal::Decimal;
13use serde::{Deserialize, Serialize};
14use std::collections::BTreeMap;
15
16/// Externally tagged value payload matching today's `ValueKind` Serialize shape.
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum ValueKind {
20    Number(Decimal),
21    Measure {
22        value: Decimal,
23    },
24    Ratio {
25        value: Decimal,
26    },
27    Text(String),
28    Date(SemanticDateTime),
29    Time(SemanticTime),
30    Boolean(bool),
31    Range {
32        from: Box<ValueKind>,
33        to: Box<ValueKind>,
34    },
35}
36
37impl ValueKind {
38    fn from_non_range(value: &DomainValueKind) -> Self {
39        match value {
40            DomainValueKind::Number(rational) => Self::Number(
41                rational
42                    .try_to_decimal()
43                    .expect("BUG: planned number must convert to decimal"),
44            ),
45            DomainValueKind::Measure(rational) => Self::Measure {
46                value: rational
47                    .try_to_decimal()
48                    .expect("BUG: planned measure must convert to decimal"),
49            },
50            DomainValueKind::Ratio(rational) => Self::Ratio {
51                value: rational
52                    .try_to_decimal()
53                    .expect("BUG: planned ratio must convert to decimal"),
54            },
55            DomainValueKind::Text(text) => Self::Text(text.to_string()),
56            DomainValueKind::Date(date) => Self::Date(date.clone()),
57            DomainValueKind::Time(time) => Self::Time(time.clone()),
58            DomainValueKind::Boolean(flag) => Self::Boolean(*flag),
59            DomainValueKind::Range(_, _) => {
60                unreachable!("BUG: range payloads are converted by ValueKind::from")
61            }
62            DomainValueKind::Spec(_) => {
63                unreachable!("BUG: spec values are rule results, not nested ValueKind payloads")
64            }
65            DomainValueKind::List(_) => {
66                unreachable!("BUG: list values are rule results, not nested ValueKind payloads")
67            }
68            DomainValueKind::UnscaledMeasure { .. } => {
69                unreachable!("BUG: unscaled measure outside its owning leaf")
70            }
71        }
72    }
73}
74
75enum ValueKindTask<'a> {
76    Visit(&'a DomainValueKind),
77    BuildRange,
78}
79
80impl From<&DomainValueKind> for ValueKind {
81    fn from(value: &DomainValueKind) -> Self {
82        let mut tasks = vec![ValueKindTask::Visit(value)];
83        let mut converted: Vec<ValueKind> = Vec::new();
84        while let Some(task) = tasks.pop() {
85            match task {
86                ValueKindTask::Visit(DomainValueKind::Range(from, to)) => {
87                    tasks.push(ValueKindTask::BuildRange);
88                    tasks.push(ValueKindTask::Visit(&to.value));
89                    tasks.push(ValueKindTask::Visit(&from.value));
90                }
91                ValueKindTask::Visit(other) => converted.push(ValueKind::from_non_range(other)),
92                ValueKindTask::BuildRange => {
93                    let to = converted
94                        .pop()
95                        .expect("BUG: range endpoint converted before build");
96                    let from = converted
97                        .pop()
98                        .expect("BUG: range endpoint converted before build");
99                    converted.push(Self::Range {
100                        from: Box::new(from),
101                        to: Box::new(to),
102                    });
103                }
104            }
105        }
106        converted
107            .pop()
108            .expect("BUG: value conversion yields exactly one ValueKind")
109    }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113pub struct LiteralValue {
114    pub value: ValueKind,
115}
116
117impl From<&DomainLiteralValue> for LiteralValue {
118    fn from(literal: &DomainLiteralValue) -> Self {
119        Self {
120            value: ValueKind::from(&literal.value),
121        }
122    }
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
126pub struct CalendarResult {
127    pub value: Decimal,
128    pub unit: String,
129}
130
131impl From<&DomainCalendarResult> for CalendarResult {
132    fn from(calendar: &DomainCalendarResult) -> Self {
133        Self {
134            value: calendar.value,
135            unit: calendar.unit.clone(),
136        }
137    }
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
141pub struct RangeResult {
142    pub from: RuleResultValue,
143    pub to: RuleResultValue,
144}
145
146impl From<&DomainRangeResult> for RangeResult {
147    fn from(range: &DomainRangeResult) -> Self {
148        Self {
149            from: RuleResultValue::from(&range.from),
150            to: RuleResultValue::from(&range.to),
151        }
152    }
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
156pub struct RuleResultValue {
157    #[serde(default, skip_serializing_if = "Option::is_none")]
158    pub result: Option<String>,
159    #[serde(default, skip_serializing_if = "Option::is_none")]
160    pub measure: Option<BTreeMap<String, Decimal>>,
161    #[serde(default, skip_serializing_if = "Option::is_none")]
162    pub ratio: Option<BTreeMap<String, Decimal>>,
163    #[serde(default, skip_serializing_if = "Option::is_none")]
164    pub number: Option<Decimal>,
165    #[serde(default, skip_serializing_if = "Option::is_none")]
166    pub boolean: Option<bool>,
167    #[serde(default, skip_serializing_if = "Option::is_none")]
168    pub text: Option<String>,
169    #[serde(default, skip_serializing_if = "Option::is_none")]
170    pub date: Option<SemanticDateTime>,
171    #[serde(default, skip_serializing_if = "Option::is_none")]
172    pub time: Option<SemanticTime>,
173    #[serde(default, skip_serializing_if = "Option::is_none")]
174    pub calendar: Option<CalendarResult>,
175    #[serde(default, skip_serializing_if = "Option::is_none")]
176    pub unit: Option<String>,
177    #[serde(default, skip_serializing_if = "Option::is_none")]
178    pub range: Option<Box<RangeResult>>,
179    #[serde(default, skip_serializing_if = "Option::is_none")]
180    pub spec: Option<Box<crate::result_value::SpecResult>>,
181    #[serde(default, skip_serializing_if = "Option::is_none")]
182    pub row: Option<crate::result_value::RowReference>,
183    #[serde(default, skip_serializing_if = "Option::is_none")]
184    pub list: Option<Vec<RuleResultValue>>,
185    #[serde(default, skip_serializing_if = "Option::is_none")]
186    pub fields: Option<IndexMap<String, RuleResultValue>>,
187    /// Show only: on a source-row field whose value is a reference; the input key
188    /// of the referenced data in the spec declaring the list.
189    #[serde(default, skip_serializing_if = "Option::is_none")]
190    pub reference: Option<String>,
191}
192
193/// A converted rule result value waiting for its children: range endpoints, list rows and
194/// field values, in that order, are the `child_count` results on top of the converted stack.
195struct RuleResultFrame {
196    shell: RuleResultValue,
197    has_range: bool,
198    list_len: Option<usize>,
199    field_names: Option<Vec<String>>,
200    child_count: usize,
201}
202
203enum RuleResultTask<'a> {
204    Visit(&'a DomainRuleResultValue),
205    Assemble(Box<RuleResultFrame>),
206}
207
208impl From<&DomainRuleResultValue> for RuleResultValue {
209    fn from(value: &DomainRuleResultValue) -> Self {
210        let mut tasks = vec![RuleResultTask::Visit(value)];
211        let mut converted: Vec<RuleResultValue> = Vec::new();
212        while let Some(task) = tasks.pop() {
213            match task {
214                RuleResultTask::Visit(value) => {
215                    let mut children: Vec<&DomainRuleResultValue> = Vec::new();
216                    if let Some(range) = &value.range {
217                        children.push(&range.from);
218                        children.push(&range.to);
219                    }
220                    let list_len = value.list.as_ref().map(|rows| {
221                        children.extend(rows.iter());
222                        rows.len()
223                    });
224                    let field_names = value.fields.as_ref().map(|fields| {
225                        children.extend(fields.values());
226                        fields.keys().cloned().collect()
227                    });
228                    let frame = RuleResultFrame {
229                        shell: RuleResultValue {
230                            result: value.result.clone(),
231                            measure: value.measure.clone(),
232                            ratio: value.ratio.clone(),
233                            number: value.number,
234                            boolean: value.boolean,
235                            text: value.text.clone(),
236                            date: value.date.clone(),
237                            time: value.time.clone(),
238                            calendar: value.calendar.as_ref().map(CalendarResult::from),
239                            unit: value.unit.clone(),
240                            range: None,
241                            spec: value.spec.clone(),
242                            row: value.row.clone(),
243                            list: None,
244                            fields: None,
245                            reference: value.reference.clone(),
246                        },
247                        has_range: value.range.is_some(),
248                        list_len,
249                        field_names,
250                        child_count: children.len(),
251                    };
252                    tasks.push(RuleResultTask::Assemble(Box::new(frame)));
253                    tasks.extend(children.into_iter().rev().map(RuleResultTask::Visit));
254                }
255                RuleResultTask::Assemble(frame) => {
256                    let frame = *frame;
257                    let mut children = converted
258                        .split_off(converted.len() - frame.child_count)
259                        .into_iter();
260                    let mut shell = frame.shell;
261                    if frame.has_range {
262                        let from = children
263                            .next()
264                            .expect("BUG: range value has a from endpoint");
265                        let to = children.next().expect("BUG: range value has a to endpoint");
266                        shell.range = Some(Box::new(RangeResult { from, to }));
267                    }
268                    if let Some(len) = frame.list_len {
269                        shell.list = Some(children.by_ref().take(len).collect());
270                    }
271                    if let Some(names) = frame.field_names {
272                        shell.fields = Some(names.into_iter().zip(children).collect());
273                    }
274                    converted.push(shell);
275                }
276            }
277        }
278        converted
279            .pop()
280            .expect("BUG: rule result value conversion yields exactly one value")
281    }
282}