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