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!(f, "Missing data: {data} (did you mean '{suggestion}'?)"),
38 VetoType::MissingData {
39 data,
40 suggestion: None,
41 } => write!(f, "Missing data: {data}"),
42 VetoType::MissingRowData { key } => write!(f, "Missing data: {key}"),
43 VetoType::UserDefined { message: Some(msg) } => write!(f, "{msg}"),
44 VetoType::UserDefined { message: None } => write!(f, "Vetoed"),
45 VetoType::Computation { message } => write!(f, "{message}"),
46 }
47 }
48}
49
50impl VetoType {
51 #[must_use]
52 pub fn computation(message: impl Into<String>) -> Self {
53 VetoType::Computation {
54 message: message.into(),
55 }
56 }
57
58 #[must_use]
59 pub fn missing_data(data: DataPath, suggestion: Option<String>) -> Self {
60 VetoType::MissingData { data, suggestion }
61 }
62}
63
64impl Serialize for VetoType {
65 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
66 where
67 S: serde::Serializer,
68 {
69 serializer.serialize_str(&self.to_string())
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
75#[serde(rename_all = "snake_case")]
76pub enum OperationResult {
77 Value(BoundValueKind),
79 Veto(VetoType),
81}
82
83impl OperationResult {
84 pub fn from_literal(value: LiteralValue) -> Self {
86 Self::Value(BoundValueKind::unbound(value.value))
87 }
88
89 pub fn from_bound(bound: BoundValueKind) -> Self {
90 Self::Value(bound)
91 }
92
93 pub fn vetoed(&self) -> bool {
94 matches!(self, OperationResult::Veto(_))
95 }
96
97 #[must_use]
99 pub fn is_missing_data(&self) -> bool {
100 matches!(
101 self,
102 OperationResult::Veto(VetoType::MissingData { .. } | VetoType::MissingRowData { .. })
103 )
104 }
105
106 #[must_use]
107 pub fn value(&self) -> Option<&BoundValueKind> {
108 match self {
109 OperationResult::Value(value) => Some(value),
110 OperationResult::Veto(_) => None,
111 }
112 }
113
114 #[must_use]
116 pub fn literal_value(&self) -> Option<LiteralValue> {
117 self.value().map(BoundValueKind::to_literal)
118 }
119
120 pub fn number(number: rust_decimal::Decimal) -> Self {
121 Self::from_literal(LiteralValue::number_from_decimal(number))
122 }
123
124 pub fn measure(
125 value: rust_decimal::Decimal,
126 unit: impl Into<String>,
127 lemma_type: Option<LemmaType>,
128 ) -> Self {
129 use crate::computation::rational::checked_mul;
130 let lemma_type = std::sync::Arc::new(
131 lemma_type.unwrap_or_else(|| LemmaType::primitive(TypeSpecification::measure())),
132 );
133 let unit_name = unit.into();
134 let rational = crate::literals::rational_from_parsed_decimal(value)
135 .expect("BUG: operation result measure must lift at boundary");
136 let factor = if let TypeSpecification::Measure { units, .. } = &lemma_type.specifications {
137 units
138 .get(&unit_name)
139 .map(|u| u.factor.clone())
140 .unwrap_or_else(|_| {
141 panic!(
142 "BUG: OperationResult::measure unit '{}' not declared on type",
143 unit_name
144 )
145 })
146 } else {
147 crate::computation::rational::rational_one()
148 };
149 let canonical = checked_mul(&rational, &factor)
150 .expect("BUG: measure canonicalization overflow in OperationResult::measure");
151 Self::from_bound(BoundValueKind {
152 value: crate::planning::semantics::ValueKind::Measure(canonical),
153 unit: Some(std::sync::Arc::from(unit_name)),
154 })
155 }
156
157 pub fn text(text: impl Into<String>) -> Self {
158 Self::from_literal(LiteralValue::text(text.into()))
159 }
160
161 pub fn date(date: impl Into<SemanticDateTime>) -> Self {
162 Self::from_literal(LiteralValue::date(date.into()))
163 }
164
165 pub fn time(time: impl Into<SemanticTime>) -> Self {
166 Self::from_literal(LiteralValue::time(time.into()))
167 }
168
169 pub fn boolean(boolean: bool) -> Self {
170 Self::from_literal(LiteralValue::from_bool(boolean))
171 }
172
173 pub fn ratio(rational: rust_decimal::Decimal) -> Self {
174 Self::from_literal(LiteralValue::ratio_from_decimal(rational))
175 }
176
177 pub fn veto(veto: impl Into<String>) -> Self {
178 Self::Veto(VetoType::UserDefined {
179 message: Some(veto.into()),
180 })
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::VetoType;
187 use crate::planning::semantics::DataPath;
188
189 #[test]
190 fn veto_type_serializes_as_display_string() {
191 let v = VetoType::missing_data(DataPath::new(vec![], "product".to_string()), None);
192 let json = serde_json::to_string(&v).expect("serialize");
193 assert_eq!(json, "\"Missing data: product\"");
194 }
195}