1use crate::error::{DataFakeError, Result};
2use crate::operators::FakeOperator;
3use crate::types::GenerationContext;
4use datalogic_rs::DataLogic;
5use serde_json::{Map, Value};
6use std::cell::RefCell;
7
8thread_local! {
9 static THREAD_LOCAL_DATA_LOGIC: RefCell<Option<DataLogic>> = const { RefCell::new(None) };
10}
11
12fn get_or_init_datalogic() -> &'static RefCell<Option<DataLogic>> {
13 THREAD_LOCAL_DATA_LOGIC.with(|dl_cell| {
14 let mut dl_opt = dl_cell.borrow_mut();
15 if dl_opt.is_none() {
16 let mut dl = DataLogic::new();
20 dl.add_operator("fake".to_string(), Box::new(FakeOperator));
22
23 *dl_opt = Some(dl);
24 }
25 unsafe { &*(dl_cell as *const RefCell<Option<DataLogic>>) }
27 })
28}
29
30pub struct Engine;
31
32impl Engine {
33 pub fn evaluate(expression: &Value, context: &GenerationContext) -> Result<Value> {
34 let dl_cell = get_or_init_datalogic();
36 let dl_opt = dl_cell.borrow();
37 let data_logic = dl_opt.as_ref().unwrap();
38
39 let context_json =
41 serde_json::to_value(&context.variables).map_err(DataFakeError::JsonError)?;
42
43 let compiled = data_logic.compile(expression).map_err(|e| {
45 DataFakeError::FakeOperatorError(format!("JSONLogic compilation error: {e}"))
46 })?;
47
48 data_logic
49 .evaluate_owned(&compiled, context_json)
50 .map_err(|e| {
51 DataFakeError::FakeOperatorError(format!("JSONLogic evaluation error: {e}"))
52 })
53 }
54
55 pub fn process_schema(schema: &Value, context: &GenerationContext) -> Result<Value> {
56 match schema {
59 Value::Object(obj) if obj.len() == 1 => {
60 if let Some((key, _value)) = obj.iter().next() {
62 if Self::is_jsonlogic_operator(key) {
65 return Self::evaluate(schema, context);
66 }
67 }
68 let mut result = serde_json::Map::new();
70 for (key, value) in obj {
71 result.insert(key.clone(), Self::process_schema(value, context)?);
72 }
73 Ok(Value::Object(result))
74 }
75 Value::Object(obj) => {
76 let mut result = serde_json::Map::new();
78 for (key, value) in obj {
79 result.insert(key.clone(), Self::process_schema(value, context)?);
80 }
81 Ok(Value::Object(result))
82 }
83 Value::Array(arr) => {
84 let mut result = Vec::new();
85 for item in arr {
86 result.push(Self::process_schema(item, context)?);
87 }
88 Ok(Value::Array(result))
89 }
90 _ => {
91 Ok(schema.clone())
93 }
94 }
95 }
96
97 fn is_jsonlogic_operator(key: &str) -> bool {
98 matches!(
100 key,
101 "var" | "==" | "!=" | "===" | "!==" | "!" | "!!" | "or" | "and" | "?:" | "if" |
102 ">" | ">=" | "<" | "<=" | "max" | "min" | "+" | "-" | "*" | "/" | "%" |
103 "map" | "filter" | "reduce" | "all" | "none" | "some" | "merge" | "in" |
104 "cat" | "substr" | "log" | "method" | "preserve" | "missing" | "missing_some" |
105 "fake"
107 )
108 }
109
110 pub fn generate_variables(variables: &Map<String, Value>) -> Result<Map<String, Value>> {
111 if variables.is_empty() {
112 return Ok(Map::new());
113 }
114
115 let temp_context = GenerationContext::new();
118 let variables_as_value = Value::Object(variables.clone());
119
120 match Self::process_schema(&variables_as_value, &temp_context)? {
121 Value::Object(map) => Ok(map),
122 _ => Err(DataFakeError::FakeOperatorError(
123 "Variables evaluation did not return an object".to_string(),
124 )),
125 }
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use serde_json::json;
133
134 #[test]
135 fn test_evaluate_simple_fake() {
136 let expression = json!({"fake": ["uuid"]});
137 let context = GenerationContext::new();
138 let result = Engine::evaluate(&expression, &context).unwrap();
139 assert!(result.is_string());
140 assert_eq!(result.as_str().unwrap().len(), 36);
141 }
142
143 #[test]
144 fn test_evaluate_var_reference() {
145 let expression = json!({"var": "userId"});
146 let mut context = GenerationContext::new();
147 context.set_variable("userId".to_string(), json!("test-id-123"));
148
149 let result = Engine::evaluate(&expression, &context).unwrap();
150 assert_eq!(result, json!("test-id-123"));
151 }
152
153 #[test]
154 fn test_process_schema_nested() {
155 let schema = json!({
156 "id": {"fake": ["uuid"]},
157 "user": {
158 "name": {"fake": ["name"]},
159 "email": {"fake": ["email"]}
160 }
161 });
162
163 let context = GenerationContext::new();
164 let result = Engine::process_schema(&schema, &context).unwrap();
165
166 assert!(result["id"].is_string());
167 assert!(result["user"]["name"].is_string());
168 assert!(result["user"]["email"].as_str().unwrap().contains('@'));
169 }
170
171 #[test]
172 fn test_process_schema_with_array() {
173 let schema = json!({
174 "tags": [
175 {"fake": ["word"]},
176 {"fake": ["word"]},
177 {"fake": ["word"]}
178 ]
179 });
180
181 let context = GenerationContext::new();
182 let result = Engine::process_schema(&schema, &context).unwrap();
183
184 assert!(result["tags"].is_array());
185 assert_eq!(result["tags"].as_array().unwrap().len(), 3);
186 }
187
188 #[test]
189 fn test_generate_variables() {
190 let variables = json!({
191 "userId": {"fake": ["uuid"]},
192 "timestamp": {"fake": ["u64", 1000000, 9999999]}
193 })
194 .as_object()
195 .unwrap()
196 .clone();
197
198 let result = Engine::generate_variables(&variables).unwrap();
199
200 assert!(result.contains_key("userId"));
201 assert!(result.contains_key("timestamp"));
202 assert!(result["userId"].is_string());
203 assert!(result["timestamp"].is_number());
204 }
205
206 #[test]
207 fn test_process_schema_with_cat_operator() {
208 let schema = json!({
209 "terminal": {"cat": ["ABCD", "XXXX"]},
210 "code": {"cat": [{"var": "prefix"}, "-", {"var": "suffix"}]}
211 });
212
213 let mut context = GenerationContext::new();
214 context.set_variable("prefix".to_string(), json!("PRE"));
215 context.set_variable("suffix".to_string(), json!("SUF"));
216
217 let result = Engine::process_schema(&schema, &context).unwrap();
218
219 assert_eq!(result["terminal"], "ABCDXXXX");
220 assert_eq!(result["code"], "PRE-SUF");
221 }
222
223 #[test]
224 fn test_jsonlogic_operators_in_schema() {
225 let schema = json!({
226 "isActive": {"==": [{"var": "status"}, "active"]},
227 "fullName": {"cat": [{"var": "firstName"}, " ", {"var": "lastName"}]},
228 "age": {"+": [{"var": "baseAge"}, 10]},
229 "hasDiscount": {">": [{"var": "purchases"}, 5]}
230 });
231
232 let mut context = GenerationContext::new();
233 context.set_variable("status".to_string(), json!("active"));
234 context.set_variable("firstName".to_string(), json!("John"));
235 context.set_variable("lastName".to_string(), json!("Doe"));
236 context.set_variable("baseAge".to_string(), json!(20));
237 context.set_variable("purchases".to_string(), json!(10));
238
239 let result = Engine::process_schema(&schema, &context).unwrap();
240
241 assert_eq!(result["isActive"], true);
242 assert_eq!(result["fullName"], "John Doe");
243 assert_eq!(result["age"], 30);
244 assert_eq!(result["hasDiscount"], true);
245 }
246
247 #[test]
248 fn test_preserve_structure_with_custom_operators() {
249 let schema = json!({
251 "user": {
252 "id": {"fake": ["uuid"]},
253 "profile": {
254 "name": {"fake": ["name"]},
255 "age": {"fake": ["u8", 18, 65]},
256 "nested": {
257 "email": {"fake": ["email"]},
258 "active": true,
259 "count": 42
260 }
261 }
262 },
263 "metadata": {
264 "version": "1.0",
265 "generated": {"fake": ["bool"]}
266 }
267 });
268
269 let context = GenerationContext::new();
270 let result = Engine::process_schema(&schema, &context).unwrap();
271
272 assert!(result["user"]["id"].is_string());
274 assert_eq!(result["user"]["id"].as_str().unwrap().len(), 36); assert!(result["user"]["profile"]["name"].is_string());
276 assert!(result["user"]["profile"]["age"].is_number());
277 let age = result["user"]["profile"]["age"].as_u64().unwrap();
278 assert!((18..=65).contains(&age));
279 assert!(
280 result["user"]["profile"]["nested"]["email"]
281 .as_str()
282 .unwrap()
283 .contains('@')
284 );
285 assert_eq!(result["user"]["profile"]["nested"]["active"], true);
286 assert_eq!(result["user"]["profile"]["nested"]["count"], 42);
287 assert_eq!(result["metadata"]["version"], "1.0");
288 assert!(result["metadata"]["generated"].is_boolean());
289 }
290}