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.clone()),
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        }
69    }
70}
71
72enum ValueKindTask<'a> {
73    Visit(&'a DomainValueKind),
74    BuildRange,
75}
76
77impl From<&DomainValueKind> for ValueKind {
78    fn from(value: &DomainValueKind) -> Self {
79        let mut tasks = vec![ValueKindTask::Visit(value)];
80        let mut converted: Vec<ValueKind> = Vec::new();
81        while let Some(task) = tasks.pop() {
82            match task {
83                ValueKindTask::Visit(DomainValueKind::Range(from, to)) => {
84                    tasks.push(ValueKindTask::BuildRange);
85                    tasks.push(ValueKindTask::Visit(&to.value));
86                    tasks.push(ValueKindTask::Visit(&from.value));
87                }
88                ValueKindTask::Visit(other) => converted.push(ValueKind::from_non_range(other)),
89                ValueKindTask::BuildRange => {
90                    let to = converted
91                        .pop()
92                        .expect("BUG: range endpoint converted before build");
93                    let from = converted
94                        .pop()
95                        .expect("BUG: range endpoint converted before build");
96                    converted.push(Self::Range {
97                        from: Box::new(from),
98                        to: Box::new(to),
99                    });
100                }
101            }
102        }
103        converted
104            .pop()
105            .expect("BUG: value conversion yields exactly one ValueKind")
106    }
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub struct LiteralValue {
111    pub value: ValueKind,
112}
113
114impl From<&DomainLiteralValue> for LiteralValue {
115    fn from(literal: &DomainLiteralValue) -> Self {
116        Self {
117            value: ValueKind::from(&literal.value),
118        }
119    }
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
123pub struct CalendarResult {
124    pub value: Decimal,
125    pub unit: String,
126}
127
128impl From<&DomainCalendarResult> for CalendarResult {
129    fn from(calendar: &DomainCalendarResult) -> Self {
130        Self {
131            value: calendar.value,
132            unit: calendar.unit.clone(),
133        }
134    }
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
138pub struct RangeResult {
139    pub from: RuleResultValue,
140    pub to: RuleResultValue,
141}
142
143impl From<&DomainRangeResult> for RangeResult {
144    fn from(range: &DomainRangeResult) -> Self {
145        Self {
146            from: RuleResultValue::from(&range.from),
147            to: RuleResultValue::from(&range.to),
148        }
149    }
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
153pub struct RuleResultValue {
154    #[serde(default, skip_serializing_if = "Option::is_none")]
155    pub result: Option<String>,
156    #[serde(default, skip_serializing_if = "Option::is_none")]
157    pub measure: Option<BTreeMap<String, Decimal>>,
158    #[serde(default, skip_serializing_if = "Option::is_none")]
159    pub ratio: Option<BTreeMap<String, Decimal>>,
160    #[serde(default, skip_serializing_if = "Option::is_none")]
161    pub number: Option<Decimal>,
162    #[serde(default, skip_serializing_if = "Option::is_none")]
163    pub boolean: Option<bool>,
164    #[serde(default, skip_serializing_if = "Option::is_none")]
165    pub text: Option<String>,
166    #[serde(default, skip_serializing_if = "Option::is_none")]
167    pub date: Option<SemanticDateTime>,
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    pub time: Option<SemanticTime>,
170    #[serde(default, skip_serializing_if = "Option::is_none")]
171    pub calendar: Option<CalendarResult>,
172    #[serde(default, skip_serializing_if = "Option::is_none")]
173    pub unit: Option<String>,
174    #[serde(default, skip_serializing_if = "Option::is_none")]
175    pub range: Option<Box<RangeResult>>,
176    #[serde(default, skip_serializing_if = "Option::is_none")]
177    pub spec: Option<Box<crate::result_value::SpecResult>>,
178    #[serde(default, skip_serializing_if = "Option::is_none")]
179    pub row: Option<crate::result_value::RowReference>,
180    #[serde(default, skip_serializing_if = "Option::is_none")]
181    pub list: Option<Vec<RuleResultValue>>,
182    #[serde(default, skip_serializing_if = "Option::is_none")]
183    pub fields: Option<IndexMap<String, RuleResultValue>>,
184    /// Show only: on a source-row field whose value is a reference; the input key
185    /// of the referenced data in the spec declaring the list.
186    #[serde(default, skip_serializing_if = "Option::is_none")]
187    pub reference: Option<String>,
188}
189
190/// A converted rule result value waiting for its children: range endpoints, list rows and
191/// field values, in that order, are the `child_count` results on top of the converted stack.
192struct RuleResultFrame {
193    shell: RuleResultValue,
194    has_range: bool,
195    list_len: Option<usize>,
196    field_names: Option<Vec<String>>,
197    child_count: usize,
198}
199
200enum RuleResultTask<'a> {
201    Visit(&'a DomainRuleResultValue),
202    Assemble(Box<RuleResultFrame>),
203}
204
205impl From<&DomainRuleResultValue> for RuleResultValue {
206    fn from(value: &DomainRuleResultValue) -> Self {
207        let mut tasks = vec![RuleResultTask::Visit(value)];
208        let mut converted: Vec<RuleResultValue> = Vec::new();
209        while let Some(task) = tasks.pop() {
210            match task {
211                RuleResultTask::Visit(value) => {
212                    let mut children: Vec<&DomainRuleResultValue> = Vec::new();
213                    if let Some(range) = &value.range {
214                        children.push(&range.from);
215                        children.push(&range.to);
216                    }
217                    let list_len = value.list.as_ref().map(|rows| {
218                        children.extend(rows.iter());
219                        rows.len()
220                    });
221                    let field_names = value.fields.as_ref().map(|fields| {
222                        children.extend(fields.values());
223                        fields.keys().cloned().collect()
224                    });
225                    let frame = RuleResultFrame {
226                        shell: RuleResultValue {
227                            result: value.result.clone(),
228                            measure: value.measure.clone(),
229                            ratio: value.ratio.clone(),
230                            number: value.number,
231                            boolean: value.boolean,
232                            text: value.text.clone(),
233                            date: value.date.clone(),
234                            time: value.time.clone(),
235                            calendar: value.calendar.as_ref().map(CalendarResult::from),
236                            unit: value.unit.clone(),
237                            range: None,
238                            spec: value.spec.clone(),
239                            row: value.row.clone(),
240                            list: None,
241                            fields: None,
242                            reference: value.reference.clone(),
243                        },
244                        has_range: value.range.is_some(),
245                        list_len,
246                        field_names,
247                        child_count: children.len(),
248                    };
249                    tasks.push(RuleResultTask::Assemble(Box::new(frame)));
250                    tasks.extend(children.into_iter().rev().map(RuleResultTask::Visit));
251                }
252                RuleResultTask::Assemble(frame) => {
253                    let frame = *frame;
254                    let mut children = converted
255                        .split_off(converted.len() - frame.child_count)
256                        .into_iter();
257                    let mut shell = frame.shell;
258                    if frame.has_range {
259                        let from = children
260                            .next()
261                            .expect("BUG: range value has a from endpoint");
262                        let to = children.next().expect("BUG: range value has a to endpoint");
263                        shell.range = Some(Box::new(RangeResult { from, to }));
264                    }
265                    if let Some(len) = frame.list_len {
266                        shell.list = Some(children.by_ref().take(len).collect());
267                    }
268                    if let Some(names) = frame.field_names {
269                        shell.fields = Some(names.into_iter().zip(children).collect());
270                    }
271                    converted.push(shell);
272                }
273            }
274        }
275        converted
276            .pop()
277            .expect("BUG: rule result value conversion yields exactly one value")
278    }
279}