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