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