1use std::fmt;
4
5use crate::planning::semantics::{
6 ArithmeticComputation, ComparisonComputation, DataPath, LemmaType, LiteralValue,
7 LogicalComputation, MathematicalComputation, RulePath, SemanticDateTime, SemanticDurationUnit,
8 SemanticTime, TypeSpecification,
9};
10use rust_decimal::Decimal;
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, PartialEq)]
18pub enum VetoType {
19 MissingData { data: DataPath },
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 { data } => write!(f, "Missing data: {}", data),
31 VetoType::UserDefined { message: Some(msg) } => write!(f, "{msg}"),
32 VetoType::UserDefined { message: None } => write!(f, "Vetoed"),
33 VetoType::Computation { message } => write!(f, "{message}"),
34 }
35 }
36}
37
38impl VetoType {
39 #[must_use]
40 pub fn computation(message: impl Into<String>) -> Self {
41 VetoType::Computation {
42 message: message.into(),
43 }
44 }
45}
46
47impl Serialize for VetoType {
48 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
49 where
50 S: serde::Serializer,
51 {
52 serializer.serialize_str(&self.to_string())
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize)]
58#[serde(rename_all = "snake_case")]
59pub enum OperationResult {
60 Value(Box<LiteralValue>),
62 Veto(VetoType),
64}
65
66impl OperationResult {
67 pub fn vetoed(&self) -> bool {
68 matches!(self, OperationResult::Veto(_))
69 }
70
71 #[must_use]
72 pub fn value(&self) -> Option<&LiteralValue> {
73 match self {
74 OperationResult::Value(v) => Some(v.as_ref()),
75 OperationResult::Veto(_) => None,
76 }
77 }
78
79 pub fn number(number: impl Into<Decimal>) -> Self {
80 Self::Value(Box::new(LiteralValue::number(number.into())))
81 }
82
83 pub fn scale(
84 scale: impl Into<Decimal>,
85 unit: impl Into<String>,
86 lemma_type: Option<LemmaType>,
87 ) -> Self {
88 let lemma_type =
89 lemma_type.unwrap_or_else(|| LemmaType::primitive(TypeSpecification::scale()));
90 Self::Value(Box::new(LiteralValue::scale_with_type(
91 scale.into(),
92 unit.into(),
93 lemma_type,
94 )))
95 }
96
97 pub fn text(text: impl Into<String>) -> Self {
98 Self::Value(Box::new(LiteralValue::text(text.into())))
99 }
100
101 pub fn date(date: impl Into<SemanticDateTime>) -> Self {
102 Self::Value(Box::new(LiteralValue::date(date.into())))
103 }
104
105 pub fn time(time: impl Into<SemanticTime>) -> Self {
106 Self::Value(Box::new(LiteralValue::time(time.into())))
107 }
108
109 pub fn boolean(boolean: bool) -> Self {
110 Self::Value(Box::new(LiteralValue::from_bool(boolean)))
111 }
112
113 pub fn duration(duration: impl Into<Decimal>, unit: impl Into<SemanticDurationUnit>) -> Self {
114 Self::Value(Box::new(LiteralValue::duration(
115 duration.into(),
116 unit.into(),
117 )))
118 }
119
120 pub fn ratio(ratio: impl Into<Decimal>) -> Self {
121 Self::Value(Box::new(LiteralValue::ratio(ratio.into(), None)))
122 }
123
124 pub fn veto(veto: impl Into<String>) -> Self {
125 Self::Veto(VetoType::UserDefined {
126 message: Some(veto.into()),
127 })
128 }
129}
130
131#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
133#[serde(tag = "type", content = "computation", rename_all = "snake_case")]
134pub enum ComputationKind {
135 Arithmetic(ArithmeticComputation),
136 Comparison(ComparisonComputation),
137 Logical(LogicalComputation),
138 Mathematical(MathematicalComputation),
139}
140
141#[derive(Debug, Clone, Serialize)]
143pub struct OperationRecord {
144 #[serde(flatten)]
145 pub kind: OperationKind,
146}
147
148#[derive(Debug, Clone, Serialize)]
150#[serde(tag = "type", rename_all = "snake_case")]
151pub enum OperationKind {
152 DataUsed {
153 data_ref: DataPath,
154 value: LiteralValue,
155 },
156 RuleUsed {
157 rule_path: RulePath,
158 result: OperationResult,
159 },
160 Computation {
161 kind: ComputationKind,
162 inputs: Vec<LiteralValue>,
163 result: LiteralValue,
164 },
165 RuleBranchEvaluated {
166 #[serde(skip_serializing_if = "Option::is_none")]
167 index: Option<usize>,
168 matched: bool,
169 #[serde(skip_serializing_if = "Option::is_none", default)]
170 result_value: Option<OperationResult>,
171 },
172}
173
174#[cfg(test)]
175mod computation_kind_serde_tests {
176 use super::ComputationKind;
177 use crate::parsing::ast::{
178 ArithmeticComputation, ComparisonComputation, MathematicalComputation,
179 };
180 use crate::planning::semantics::LogicalComputation;
181
182 #[test]
183 fn computation_kind_arithmetic_round_trip() {
184 let k = ComputationKind::Arithmetic(ArithmeticComputation::Add);
185 let json = serde_json::to_string(&k).expect("serialize");
186 assert!(json.contains("\"type\"") && json.contains("\"computation\""));
187 let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
188 assert_eq!(back, k);
189 }
190
191 #[test]
192 fn computation_kind_comparison_round_trip() {
193 let k = ComputationKind::Comparison(ComparisonComputation::GreaterThan);
194 let json = serde_json::to_string(&k).expect("serialize");
195 let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
196 assert_eq!(back, k);
197 }
198
199 #[test]
200 fn computation_kind_logical_round_trip() {
201 let k = ComputationKind::Logical(LogicalComputation::And);
202 let json = serde_json::to_string(&k).expect("serialize");
203 let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
204 assert_eq!(back, k);
205 }
206
207 #[test]
208 fn computation_kind_mathematical_round_trip() {
209 let k = ComputationKind::Mathematical(MathematicalComputation::Sqrt);
210 let json = serde_json::to_string(&k).expect("serialize");
211 let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
212 assert_eq!(back, k);
213 }
214
215 #[test]
216 fn veto_type_serializes_as_display_string() {
217 use super::VetoType;
218 use crate::planning::semantics::DataPath;
219 let v = VetoType::MissingData {
220 data: DataPath::new(vec![], "product".to_string()),
221 };
222 let json = serde_json::to_string(&v).expect("serialize");
223 assert_eq!(json, "\"Missing data: product\"");
224 }
225}