oca_bundle/state/oca/overlay/
conditional.rs1use crate::state::{attribute::Attribute, oca::Overlay};
2use oca_ast::ast::OverlayType;
3use piccolo::{Closure, Lua, Thread};
4use said::{sad::SerializationFormats, sad::SAD};
5use serde::{Deserialize, Serialize};
6use std::{any::Any, collections::BTreeMap, error::Error as StdError, fmt::Display};
7
8pub trait Conditionals {
9 fn set_condition(&mut self, condition: String);
10 fn check_condition(
11 &self,
12 dependency_values: BTreeMap<String, Box<dyn Display + 'static>>,
13 ) -> Result<bool, Vec<Error>>;
14}
15
16#[derive(Serialize, Deserialize, Debug)]
17#[serde(untagged)]
18pub enum Error {
19 Custom(String),
20}
21
22impl Conditionals for Attribute {
23 fn set_condition(&mut self, condition: String) {
24 let re = regex::Regex::new(r"\$\{([^}]*)\}").unwrap();
25 let mut dependencies = Vec::new();
26
27 let cond = re
28 .replace_all(&condition, |caps: ®ex::Captures| {
29 let i = dependencies
30 .iter()
31 .position(|d| d == &caps[1])
32 .unwrap_or_else(|| {
33 dependencies.push(caps[1].to_string());
34 dependencies.len() - 1
35 });
36 format!("${{{}}}", i)
37 })
38 .to_string();
39
40 self.condition = Some(cond);
41 self.dependencies = Some(dependencies);
42 }
43
44 fn check_condition(
45 &self,
46 dependency_values: BTreeMap<String, Box<dyn Display + 'static>>,
47 ) -> Result<bool, Vec<Error>> {
48 let mut errors: Vec<Error> = vec![];
49
50 let condition = &self.condition.clone().ok_or(vec![Error::Custom(
51 "Attribute has no condition".to_string(),
52 )])?;
53 let condition_dependencies = &self.dependencies.clone().unwrap();
54 let re = regex::Regex::new(r"\$\{(\d+)\}").unwrap();
55 let attr = &self.name;
56 if condition_dependencies.contains(attr) {
57 errors.push(Error::Custom(format!(
58 "Attribute '{attr}' cannot be a dependency of itself"
59 )));
60 }
61 condition_dependencies.iter().for_each(|d| {
62 if !dependency_values.contains_key(d) {
63 errors.push(Error::Custom(format!("Missing dependency '{d}' value",)));
64 }
65 });
66
67 if !errors.is_empty() {
68 return Err(errors);
69 }
70
71 let script = re
72 .replace_all(condition, |caps: ®ex::Captures| {
73 dependency_values
74 .get(&condition_dependencies[caps[1].parse::<usize>().unwrap()].clone())
75 .unwrap()
76 .to_string()
77 })
78 .to_string();
79
80 let mut lua = Lua::new();
81 let thread_result = lua.try_run(|ctx| {
82 let closure = Closure::load(ctx, format!("return {script}").as_bytes())?;
83 let thread = Thread::new(&ctx);
84 thread.start(ctx, closure.into(), ())?;
85 Ok(ctx.state.registry.stash(&ctx, thread))
86 });
87
88 let mut result = None;
89 match thread_result {
90 Ok(thread) => match lua.run_thread::<bool>(&thread) {
91 Ok(r) => result = Some(r),
92 Err(e) => {
93 errors.push(Error::Custom(format!(
94 "Attribute '{attr}' has invalid condition: {}",
95 e.source().unwrap()
96 )));
97 }
98 },
99 Err(e) => {
100 errors.push(Error::Custom(format!(
101 "Attribute '{attr}' has invalid condition: {}",
102 e.source().unwrap()
103 )));
104 }
105 };
106 if errors.is_empty() {
107 match result.is_some() {
108 true => Ok(result.unwrap()),
109 false => Err(vec![Error::Custom(format!(
110 "Attribute '{attr}' has invalid condition",
111 ))]),
112 }
113 } else {
114 Err(errors)
115 }
116 }
117}
118
119#[derive(SAD, Serialize, Deserialize, Debug, Clone)]
120pub struct ConditionalOverlay {
121 #[said]
122 #[serde(rename = "d")]
123 said: Option<said::SelfAddressingIdentifier>,
124 #[serde(rename = "type")]
125 overlay_type: OverlayType,
126 capture_base: Option<said::SelfAddressingIdentifier>,
127 pub attribute_conditions: BTreeMap<String, String>,
128 pub attribute_dependencies: BTreeMap<String, Vec<String>>,
129}
130
131impl Overlay for ConditionalOverlay {
132 fn as_any(&self) -> &dyn Any {
133 self
134 }
135 fn capture_base(&self) -> &Option<said::SelfAddressingIdentifier> {
136 &self.capture_base
137 }
138 fn set_capture_base(&mut self, said: &said::SelfAddressingIdentifier) {
139 self.capture_base = Some(said.clone());
140 }
141 fn said(&self) -> &Option<said::SelfAddressingIdentifier> {
142 &self.said
143 }
144 fn overlay_type(&self) -> &OverlayType {
145 &self.overlay_type
146 }
147 fn attributes(&self) -> Vec<&String> {
148 self.attribute_conditions.keys().collect::<Vec<&String>>()
149 }
150
151 fn add(&mut self, attribute: &Attribute) {
152 if attribute.condition.is_some() {
153 self.attribute_conditions.insert(
154 attribute.name.clone(),
155 attribute.condition.as_ref().unwrap().clone(),
156 );
157 }
158 if attribute.dependencies.is_some() {
159 self.attribute_dependencies.insert(
160 attribute.name.clone(),
161 attribute.dependencies.as_ref().unwrap().clone(),
162 );
163 }
164 }
165}
166impl ConditionalOverlay {
167 pub fn new() -> Self {
168 Self {
169 capture_base: None,
170 said: None,
171 overlay_type: OverlayType::Conditional,
172 attribute_conditions: BTreeMap::new(),
173 attribute_dependencies: BTreeMap::new(),
174 }
175 }
176}
177
178impl Default for ConditionalOverlay {
179 fn default() -> Self {
180 Self::new()
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use oca_ast::ast::{AttributeType, NestedAttrType};
188
189 struct Dependency {
190 name: String,
191 value: Box<dyn Display + 'static>,
192 }
193 impl core::fmt::Debug for Dependency {
194 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
195 write!(f, "{}={}", self.name, self.value)
196 }
197 }
198 impl Dependency {
199 fn new(name: &str, value: Box<dyn Display + 'static>) -> Self {
200 Self {
201 name: name.to_string(),
202 value,
203 }
204 }
205 }
206
207 #[test]
208 fn test_checking_condition() {
209 let setting: Vec<(&str, Vec<Dependency>, bool)> = vec![
210 (
211 "${age} > 18 and ${age} < 30",
212 vec![
213 Dependency::new("age", Box::new(20)),
214 Dependency::new("name", Box::new("new")),
215 ],
216 true,
217 ),
218 (
219 "${age} > 18 and ${age} < 30",
220 vec![Dependency::new("age", Box::new(30))],
221 false,
222 ),
223 (
224 "${age} > 18 and ${age} < 30",
225 vec![Dependency::new("age", Box::new(18))],
226 false,
227 ),
228 (
229 "${age} > 18 and ${age} < 30",
230 vec![Dependency::new("age", Box::new(31))],
231 false,
232 ),
233 (
234 "${age} > 18 and ${age} < 30",
235 vec![Dependency::new("age", Box::new(17))],
236 false,
237 ),
238 (
239 "2021-01-01 > ${start_date}",
240 vec![Dependency::new("start_date", Box::new("2024-01-01"))],
241 false,
242 ),
243 ];
244
245 for (condition, values, expected) in setting {
246 let attribute = cascade! {
247 Attribute::new("name".to_string());
248 ..set_attribute_type(NestedAttrType::Value(AttributeType::Text));
249 ..set_condition(condition.to_string());
250 };
251
252 let mut dependency_values: BTreeMap<String, Box<dyn Display + 'static>> =
253 BTreeMap::new();
254 let mut dependency_values_str = vec![];
255 for value in values.into_iter() {
256 dependency_values_str.push(format!("{:?}", value));
257 dependency_values.insert(value.name, value.value);
258 }
259
260 let r = attribute.check_condition(dependency_values);
261 assert_eq!(
262 r.unwrap(),
263 expected,
264 "condition: \"{}\", values: [{}]",
265 condition,
266 dependency_values_str.join(", ")
267 );
268 }
269 }
270}