1use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct CatalogRule {
9 pub id: Uuid,
10 pub name: String,
11 pub rule_type: RuleType,
12 pub conditions: Vec<RuleCondition>,
13 pub logical_operator: LogicalOperator, pub actions: Vec<RuleAction>,
15 pub priority: u32,
16 pub is_active: bool,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub parent_rule_id: Option<Uuid>, #[serde(skip_serializing_if = "Option::is_none")]
20 pub valid_for: Option<TimePeriod>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct TimePeriod {
26 pub start: chrono::DateTime<chrono::Utc>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub end: Option<chrono::DateTime<chrono::Utc>>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
34pub enum RuleType {
35 Validation,
36 Transformation,
37 Pricing,
38 Eligibility,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct RuleCondition {
44 pub field: String,
45 pub operator: RuleOperator,
46 pub value: String,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub value2: Option<String>, }
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
54pub enum LogicalOperator {
55 And,
56 Or,
57 Not,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
62#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
63pub enum RuleOperator {
64 Equals,
65 NotEquals,
66 GreaterThan,
67 LessThan,
68 GreaterThanOrEqual,
69 LessThanOrEqual,
70 Contains,
71 NotContains,
72 StartsWith,
73 EndsWith,
74 Regex,
75 In,
76 NotIn,
77 Between,
78 IsNull,
79 IsNotNull,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct RuleAction {
85 pub action_type: ActionType,
86 pub target: String,
87 pub value: String,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
92#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
93pub enum ActionType {
94 Set,
95 Add,
96 Remove,
97 Validate,
98 Transform,
99}
100
101pub fn evaluate_rule(rule: &CatalogRule, context: &RuleContext) -> RuleResult {
103 if !rule.is_active {
104 return RuleResult::Skipped;
105 }
106
107 if let Some(ref period) = rule.valid_for {
109 let now = chrono::Utc::now();
110 if now < period.start {
111 return RuleResult::NotMatched; }
113 if let Some(end) = period.end {
114 if now > end {
115 return RuleResult::NotMatched; }
117 }
118 }
119
120 let conditions_met = match rule.logical_operator {
122 LogicalOperator::And => rule
123 .conditions
124 .iter()
125 .all(|condition| evaluate_condition(condition, context)),
126 LogicalOperator::Or => rule
127 .conditions
128 .iter()
129 .any(|condition| evaluate_condition(condition, context)),
130 LogicalOperator::Not => !rule
131 .conditions
132 .iter()
133 .all(|condition| evaluate_condition(condition, context)),
134 };
135
136 if conditions_met {
137 RuleResult::Matched {
138 actions: rule.actions.clone(),
139 }
140 } else {
141 RuleResult::NotMatched
142 }
143}
144
145#[derive(Debug, Clone)]
147pub struct RuleContext {
148 pub data: std::collections::HashMap<String, String>,
149}
150
151#[derive(Debug, Clone)]
153pub enum RuleResult {
154 Matched { actions: Vec<RuleAction> },
155 NotMatched,
156 Skipped,
157}
158
159fn evaluate_condition(condition: &RuleCondition, context: &RuleContext) -> bool {
160 let field_value = context.data.get(&condition.field).cloned();
161
162 match condition.operator {
163 RuleOperator::IsNull => field_value.is_none(),
164 RuleOperator::IsNotNull => field_value.is_some(),
165 _ => {
166 let field_value = field_value.unwrap_or_default();
167 match condition.operator {
168 RuleOperator::Equals => field_value == condition.value,
169 RuleOperator::NotEquals => field_value != condition.value,
170 RuleOperator::GreaterThan => {
171 if let (Ok(field_num), Ok(cond_num)) =
172 (field_value.parse::<f64>(), condition.value.parse::<f64>())
173 {
174 field_num > cond_num
175 } else {
176 false
177 }
178 }
179 RuleOperator::LessThan => {
180 if let (Ok(field_num), Ok(cond_num)) =
181 (field_value.parse::<f64>(), condition.value.parse::<f64>())
182 {
183 field_num < cond_num
184 } else {
185 false
186 }
187 }
188 RuleOperator::GreaterThanOrEqual => {
189 if let (Ok(field_num), Ok(cond_num)) =
190 (field_value.parse::<f64>(), condition.value.parse::<f64>())
191 {
192 field_num >= cond_num
193 } else {
194 false
195 }
196 }
197 RuleOperator::LessThanOrEqual => {
198 if let (Ok(field_num), Ok(cond_num)) =
199 (field_value.parse::<f64>(), condition.value.parse::<f64>())
200 {
201 field_num <= cond_num
202 } else {
203 false
204 }
205 }
206 RuleOperator::Contains => field_value.contains(&condition.value),
207 RuleOperator::NotContains => !field_value.contains(&condition.value),
208 RuleOperator::StartsWith => field_value.starts_with(&condition.value),
209 RuleOperator::EndsWith => field_value.ends_with(&condition.value),
210 RuleOperator::Regex => regex::Regex::new(&condition.value)
211 .map(|re| re.is_match(&field_value))
212 .unwrap_or(false),
213 RuleOperator::In => {
214 let values: Vec<&str> = condition.value.split(',').collect();
215 values.iter().any(|v| v.trim() == field_value)
216 }
217 RuleOperator::NotIn => {
218 let values: Vec<&str> = condition.value.split(',').collect();
219 !values.iter().any(|v| v.trim() == field_value)
220 }
221 RuleOperator::Between => {
222 if let Some(ref value2_str) = condition.value2 {
223 if let (Ok(field_num), Ok(min), Ok(max)) = (
224 field_value.parse::<f64>(),
225 condition.value.parse::<f64>(),
226 value2_str.parse::<f64>(),
227 ) {
228 field_num >= min && field_num <= max
229 } else {
230 false
231 }
232 } else {
233 false
234 }
235 }
236 _ => false,
237 }
238 }
239 }
240}