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