lemma/computation/
operation_result.rs1use std::fmt;
4
5use crate::planning::semantics::{
6 BoundValueKind, DataPath, LemmaType, LiteralValue, SemanticDateTime, SemanticTime,
7 TypeSpecification,
8};
9use serde::Serialize;
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
16pub enum VetoType {
17 MissingData {
19 data: DataPath,
20 suggestion: Option<String>,
21 },
22 MissingRowData { key: String },
25 UserDefined { message: Option<String> },
27 Computation { message: String },
29}
30
31impl fmt::Display for VetoType {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 match self {
34 VetoType::MissingData {
35 data,
36 suggestion: Some(suggestion),
37 } => write!(
38 f,
39 "Required data '{data}' was not provided. Did you mean '{suggestion}'?"
40 ),
41 VetoType::MissingData {
42 data,
43 suggestion: None,
44 } => write!(f, "Required data '{data}' was not provided."),
45 VetoType::MissingRowData { key } => match split_numbered_row_field_key(key) {
46 Some((list, number, field)) => write!(
47 f,
48 "Required field '{field}' was not provided on row {number} of list '{list}'."
49 ),
50 None => write!(f, "Required data '{key}' was not provided."),
51 },
52 VetoType::UserDefined { message: Some(msg) } => write!(f, "{msg}"),
53 VetoType::UserDefined { message: None } => write!(f, "This rule was vetoed."),
54 VetoType::Computation { message } => write!(f, "{message}"),
55 }
56 }
57}
58
59fn split_numbered_row_field_key(key: &str) -> Option<(&str, &str, &str)> {
61 let (list_and_number, field) = key.rsplit_once('.')?;
62 let (list, number) = list_and_number.rsplit_once('.')?;
63 if number.is_empty() || !number.bytes().all(|b| b.is_ascii_digit()) {
64 return None;
65 }
66 if list.is_empty() || field.is_empty() {
67 return None;
68 }
69 Some((list, number, field))
70}
71
72impl VetoType {
73 #[must_use]
74 pub fn computation(message: impl Into<String>) -> Self {
75 VetoType::Computation {
76 message: message.into(),
77 }
78 }
79
80 #[must_use]
81 pub fn missing_data(data: DataPath, suggestion: Option<String>) -> Self {
82 VetoType::MissingData { data, suggestion }
83 }
84}
85
86impl Serialize for VetoType {
87 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
88 where
89 S: serde::Serializer,
90 {
91 serializer.serialize_str(&self.to_string())
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
97#[serde(rename_all = "snake_case")]
98pub enum OperationResult {
99 Value(BoundValueKind),
101 Veto(VetoType),
103}
104
105impl OperationResult {
106 pub fn from_literal(value: LiteralValue) -> Self {
108 Self::Value(BoundValueKind::unbound(value.value))
109 }
110
111 pub fn from_bound(bound: BoundValueKind) -> Self {
112 Self::Value(bound)
113 }
114
115 pub fn vetoed(&self) -> bool {
116 matches!(self, OperationResult::Veto(_))
117 }
118
119 #[must_use]
121 pub fn is_missing_data(&self) -> bool {
122 matches!(
123 self,
124 OperationResult::Veto(VetoType::MissingData { .. } | VetoType::MissingRowData { .. })
125 )
126 }
127
128 #[must_use]
129 pub fn value(&self) -> Option<&BoundValueKind> {
130 match self {
131 OperationResult::Value(value) => Some(value),
132 OperationResult::Veto(_) => None,
133 }
134 }
135
136 #[must_use]
138 pub fn literal_value(&self) -> Option<LiteralValue> {
139 self.value().map(BoundValueKind::to_literal)
140 }
141
142 pub fn number(number: rust_decimal::Decimal) -> Self {
143 Self::from_literal(LiteralValue::number_from_decimal(number))
144 }
145
146 pub fn measure(
147 value: rust_decimal::Decimal,
148 unit: impl Into<String>,
149 lemma_type: Option<LemmaType>,
150 ) -> Self {
151 use crate::computation::rational::checked_mul;
152 let lemma_type = std::sync::Arc::new(
153 lemma_type.unwrap_or_else(|| LemmaType::primitive(TypeSpecification::measure())),
154 );
155 let unit_name = unit.into();
156 let rational = crate::literals::rational_from_parsed_decimal(value)
157 .expect("BUG: operation result measure must lift at boundary");
158 let factor = if let TypeSpecification::Measure { units, .. } = &lemma_type.specifications {
159 units
160 .get(&unit_name)
161 .map(|u| u.factor.clone())
162 .unwrap_or_else(|_| {
163 panic!(
164 "BUG: OperationResult::measure unit '{}' not declared on type",
165 unit_name
166 )
167 })
168 } else {
169 crate::computation::rational::rational_one()
170 };
171 let canonical = checked_mul(&rational, &factor)
172 .expect("BUG: measure canonicalization overflow in OperationResult::measure");
173 Self::from_bound(BoundValueKind {
174 value: crate::planning::semantics::ValueKind::Measure(canonical),
175 unit: Some(std::sync::Arc::from(unit_name)),
176 })
177 }
178
179 pub fn text(text: impl Into<String>) -> Self {
180 Self::from_literal(LiteralValue::text(text.into()))
181 }
182
183 pub fn date(date: impl Into<SemanticDateTime>) -> Self {
184 Self::from_literal(LiteralValue::date(date.into()))
185 }
186
187 pub fn time(time: impl Into<SemanticTime>) -> Self {
188 Self::from_literal(LiteralValue::time(time.into()))
189 }
190
191 pub fn boolean(boolean: bool) -> Self {
192 Self::from_literal(LiteralValue::from_bool(boolean))
193 }
194
195 pub fn ratio(rational: rust_decimal::Decimal) -> Self {
196 Self::from_literal(LiteralValue::ratio_from_decimal(rational))
197 }
198
199 pub fn veto(veto: impl Into<String>) -> Self {
200 Self::Veto(VetoType::UserDefined {
201 message: Some(veto.into()),
202 })
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::VetoType;
209 use crate::planning::semantics::DataPath;
210
211 #[test]
212 fn veto_type_serializes_as_display_string() {
213 let v = VetoType::missing_data(DataPath::new(vec![], "product".to_string()), None);
214 let json = serde_json::to_string(&v).expect("serialize");
215 assert_eq!(json, "\"Required data 'product' was not provided.\"");
216 }
217
218 #[test]
219 fn missing_row_data_display_names_list_row_and_field() {
220 let v = VetoType::MissingRowData {
221 key: "menu.2.price".to_string(),
222 };
223 assert_eq!(
224 v.to_string(),
225 "Required field 'price' was not provided on row 2 of list 'menu'."
226 );
227 }
228}