Skip to main content

json_eval_rs/jsoneval/
core.rs

1use super::JSONEval;
2use crate::jsoneval::eval_data::EvalData;
3use crate::jsoneval::json_parser;
4use crate::parse_schema;
5use crate::jsoneval::parsed_schema::ParsedSchema;
6use crate::jsoneval::parsed_schema_cache::PARSED_SCHEMA_CACHE;
7use crate::rlogic::{RLogic, RLogicConfig};
8
9use crate::time_block;
10
11use indexmap::IndexMap;
12use serde::de::Error as _;
13use serde_json::Value;
14use std::collections::HashMap;
15use std::sync::{Arc, Mutex, RwLock};
16
17
18impl Clone for JSONEval {
19    fn clone(&self) -> Self {
20        Self {
21            schema: Arc::clone(&self.schema),
22            engine: Arc::clone(&self.engine),
23            evaluations: self.evaluations.clone(),
24            tables: self.tables.clone(),
25            table_metadata: self.table_metadata.clone(),
26            dependencies: self.dependencies.clone(),
27            sorted_evaluations: self.sorted_evaluations.clone(),
28            dependents_evaluations: self.dependents_evaluations.clone(),
29            rules_evaluations: self.rules_evaluations.clone(),
30            fields_with_rules: self.fields_with_rules.clone(),
31            others_evaluations: self.others_evaluations.clone(),
32            value_evaluations: self.value_evaluations.clone(),
33            layout_paths: self.layout_paths.clone(),
34            options_templates: self.options_templates.clone(),
35            subforms: self.subforms.clone(),
36            reffed_by: self.reffed_by.clone(),
37            context: self.context.clone(),
38            data: self.data.clone(),
39            evaluated_schema: self.evaluated_schema.clone(),
40            eval_data: self.eval_data.clone(),
41            eval_lock: Mutex::new(()),    // Create fresh mutex for the clone
42            cached_msgpack_schema: self.cached_msgpack_schema.clone(),
43            conditional_hidden_fields: self.conditional_hidden_fields.clone(),
44            conditional_readonly_fields: self.conditional_readonly_fields.clone(),
45            regex_cache: RwLock::new(HashMap::new()),
46        }
47    }
48}
49
50impl JSONEval {
51    pub fn new(
52        schema: &str,
53        context: Option<&str>,
54        data: Option<&str>,
55    ) -> Result<Self, serde_json::Error> {
56        time_block!("JSONEval::new() [total]", {
57            // Use serde_json for schema (needs arbitrary_precision) and SIMD for data (needs speed)
58            let schema_val: Value =
59                time_block!("  parse schema JSON", { serde_json::from_str(schema)? });
60            let context: Value = time_block!("  parse context JSON", {
61                json_parser::parse_json_str(context.unwrap_or("{}"))
62                    .map_err(serde_json::Error::custom)?
63            });
64            let data: Value = time_block!("  parse data JSON", {
65                json_parser::parse_json_str(data.unwrap_or("{}"))
66                    .map_err(serde_json::Error::custom)?
67            });
68            let evaluated_schema = schema_val.clone();
69            // Use default config: tracking enabled
70            let engine_config = RLogicConfig::default();
71
72            let mut instance = time_block!("  create instance struct", {
73                Self {
74                    schema: Arc::new(schema_val),
75                    evaluations: Arc::new(IndexMap::new()),
76                    tables: Arc::new(IndexMap::new()),
77                    table_metadata: Arc::new(IndexMap::new()),
78                    dependencies: Arc::new(IndexMap::new()),
79                    sorted_evaluations: Arc::new(Vec::new()),
80                    dependents_evaluations: Arc::new(IndexMap::new()),
81                    rules_evaluations: Arc::new(Vec::new()),
82                    fields_with_rules: Arc::new(Vec::new()),
83                    others_evaluations: Arc::new(Vec::new()),
84                    value_evaluations: Arc::new(Vec::new()),
85                    layout_paths: Arc::new(Vec::new()),
86                    options_templates: Arc::new(Vec::new()),
87                    subforms: IndexMap::new(),
88                    engine: Arc::new(RLogic::with_config(engine_config)),
89                    reffed_by: Arc::new(IndexMap::new()),
90                    context: context.clone(),
91                    data: data.clone(),
92                    evaluated_schema: evaluated_schema.clone(),
93                    eval_data: EvalData::with_schema_data_context(
94                        &evaluated_schema,
95                        &data,
96                        &context,
97                    ),
98                    eval_lock: Mutex::new(()),
99                    cached_msgpack_schema: None,
100                    conditional_hidden_fields: Arc::new(Vec::new()),
101                    conditional_readonly_fields: Arc::new(Vec::new()),
102                    regex_cache: RwLock::new(HashMap::new()),
103                }
104            });
105            time_block!("  parse_schema", {
106                parse_schema::legacy::parse_schema(&mut instance)
107                    .map_err(serde_json::Error::custom)?
108            });
109            Ok(instance)
110        })
111    }
112
113    /// Create a new JSONEval instance from MessagePack-encoded schema
114    ///
115    /// # Arguments
116    ///
117    /// * `schema_msgpack` - MessagePack-encoded schema bytes
118    /// * `context` - Optional JSON context string
119    /// * `data` - Optional JSON data string
120    ///
121    /// # Returns
122    ///
123    /// A Result containing the JSONEval instance or an error
124    pub fn new_from_msgpack(
125        schema_msgpack: &[u8],
126        context: Option<&str>,
127        data: Option<&str>,
128    ) -> Result<Self, String> {
129        // Store original MessagePack bytes for zero-copy retrieval
130        let cached_msgpack = schema_msgpack.to_vec();
131
132        // Deserialize MessagePack schema to Value
133        let schema_val: Value = rmp_serde::from_slice(schema_msgpack)
134            .map_err(|e| format!("Failed to deserialize MessagePack schema: {}", e))?;
135
136        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))
137            .map_err(|e| format!("Failed to parse context: {}", e))?;
138        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))
139            .map_err(|e| format!("Failed to parse data: {}", e))?;
140        let evaluated_schema = schema_val.clone();
141        let engine_config = RLogicConfig::default();
142
143        let mut instance = Self {
144            schema: Arc::new(schema_val),
145            evaluations: Arc::new(IndexMap::new()),
146            tables: Arc::new(IndexMap::new()),
147            table_metadata: Arc::new(IndexMap::new()),
148            dependencies: Arc::new(IndexMap::new()),
149            sorted_evaluations: Arc::new(Vec::new()),
150            dependents_evaluations: Arc::new(IndexMap::new()),
151            rules_evaluations: Arc::new(Vec::new()),
152            fields_with_rules: Arc::new(Vec::new()),
153            others_evaluations: Arc::new(Vec::new()),
154            value_evaluations: Arc::new(Vec::new()),
155            layout_paths: Arc::new(Vec::new()),
156            options_templates: Arc::new(Vec::new()),
157            subforms: IndexMap::new(),
158            engine: Arc::new(RLogic::with_config(engine_config)),
159            reffed_by: Arc::new(IndexMap::new()),
160            context: context.clone(),
161            data: data.clone(),
162            evaluated_schema: evaluated_schema.clone(),
163            eval_data: EvalData::with_schema_data_context(&evaluated_schema, &data, &context),
164            eval_lock: Mutex::new(()),
165            cached_msgpack_schema: Some(cached_msgpack),
166            conditional_hidden_fields: Arc::new(Vec::new()),
167            conditional_readonly_fields: Arc::new(Vec::new()),
168            regex_cache: RwLock::new(HashMap::new()),
169        };
170        parse_schema::legacy::parse_schema(&mut instance)?;
171        Ok(instance)
172    }
173
174    /// Create a new JSONEval instance from a pre-parsed ParsedSchema
175    ///
176    /// This enables schema caching: parse once, reuse across multiple evaluations with different data/context.
177    ///
178    /// # Arguments
179    ///
180    /// * `parsed` - Arc-wrapped pre-parsed schema (can be cloned and cached)
181    /// * `context` - Optional JSON context string
182    /// * `data` - Optional JSON data string
183    ///
184    /// # Returns
185    ///
186    /// A Result containing the JSONEval instance or an error
187    ///
188    /// # Example
189    ///
190    /// ```ignore
191    /// use std::sync::Arc;
192    ///
193    /// // Parse schema once and wrap in Arc for caching
194    /// let parsed = Arc::new(ParsedSchema::parse(schema_str)?);
195    /// cache.insert(schema_key, parsed.clone());
196    ///
197    /// // Reuse across multiple evaluations (Arc::clone is cheap)
198    /// let eval1 = JSONEval::with_parsed_schema(parsed.clone(), Some(context1), Some(data1))?;
199    /// let eval2 = JSONEval::with_parsed_schema(parsed.clone(), Some(context2), Some(data2))?;
200    /// ```
201    pub fn with_parsed_schema(
202        parsed: Arc<ParsedSchema>,
203        context: Option<&str>,
204        data: Option<&str>,
205    ) -> Result<Self, String> {
206        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))
207            .map_err(|e| format!("Failed to parse context: {}", e))?;
208        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))
209            .map_err(|e| format!("Failed to parse data: {}", e))?;
210
211        let evaluated_schema = parsed.schema.clone();
212
213        // Share the engine Arc (cheap pointer clone, not data clone)
214        // Multiple JSONEval instances created from the same ParsedSchema will share the compiled RLogic
215        let engine = parsed.engine.clone();
216
217        // Convert Arc<ParsedSchema> subforms to Box<JSONEval> subforms
218        // This is a one-time conversion when creating JSONEval from ParsedSchema
219        let mut subforms = IndexMap::new();
220        for (path, subform_parsed) in &parsed.subforms {
221            // Create JSONEval from the cached ParsedSchema
222            let subform_eval =
223                JSONEval::with_parsed_schema(subform_parsed.clone(), Some("{}"), None)?;
224            subforms.insert(path.clone(), Box::new(subform_eval));
225        }
226
227        let instance = Self {
228            schema: Arc::clone(&parsed.schema),
229            evaluations: Arc::clone(&parsed.evaluations),
230            tables: Arc::clone(&parsed.tables),
231            table_metadata: Arc::clone(&parsed.table_metadata),
232            dependencies: Arc::clone(&parsed.dependencies),
233            sorted_evaluations: Arc::clone(&parsed.sorted_evaluations),
234            dependents_evaluations: Arc::clone(&parsed.dependents_evaluations),
235            rules_evaluations: Arc::clone(&parsed.rules_evaluations),
236            fields_with_rules: Arc::clone(&parsed.fields_with_rules),
237            others_evaluations: Arc::clone(&parsed.others_evaluations),
238            value_evaluations: Arc::clone(&parsed.value_evaluations),
239            layout_paths: Arc::clone(&parsed.layout_paths),
240            options_templates: Arc::clone(&parsed.options_templates),
241            subforms,
242            engine,
243            reffed_by: Arc::clone(&parsed.reffed_by),
244            context: context.clone(),
245            data: data.clone(),
246            evaluated_schema: (*evaluated_schema).clone(),
247            eval_data: EvalData::with_schema_data_context(&evaluated_schema, &data, &context),
248            eval_lock: Mutex::new(()),
249            cached_msgpack_schema: None,
250            conditional_hidden_fields: Arc::clone(&parsed.conditional_hidden_fields),
251            conditional_readonly_fields: Arc::clone(&parsed.conditional_readonly_fields),
252            regex_cache: RwLock::new(HashMap::new()),
253        };
254        Ok(instance)
255    }
256
257    pub fn reload_schema(
258        &mut self,
259        schema: &str,
260        context: Option<&str>,
261        data: Option<&str>,
262    ) -> Result<(), String> {
263        // Use serde_json for schema (precision) and SIMD for data (speed)
264        let schema_val: Value =
265            serde_json::from_str(schema).map_err(|e| format!("failed to parse schema: {e}"))?;
266        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))?;
267        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))?;
268        self.schema = Arc::new(schema_val);
269        self.context = context.clone();
270        self.data = data.clone();
271        self.evaluated_schema = (*self.schema).clone();
272        self.engine = Arc::new(RLogic::new());
273        self.dependents_evaluations = Arc::new(IndexMap::new());
274        self.rules_evaluations = Arc::new(Vec::new());
275        self.fields_with_rules = Arc::new(Vec::new());
276        self.others_evaluations = Arc::new(Vec::new());
277        self.value_evaluations = Arc::new(Vec::new());
278        self.layout_paths = Arc::new(Vec::new());
279        self.options_templates = Arc::new(Vec::new());
280        self.subforms.clear();
281        parse_schema::legacy::parse_schema(self)?;
282
283        // Re-initialize eval_data with new schema, data, and context
284        self.eval_data =
285            EvalData::with_schema_data_context(&self.evaluated_schema, &data, &context);
286
287        // Clear MessagePack cache since schema has been mutated
288        self.cached_msgpack_schema = None;
289
290        Ok(())
291    }
292
293    /// Set the timezone offset for datetime operations (TODAY, NOW)
294    ///
295    /// This method updates the RLogic engine configuration with a new timezone offset.
296    /// The offset will be applied to all subsequent datetime evaluations.
297    ///
298    /// # Arguments
299    ///
300    /// * `offset_minutes` - Timezone offset in minutes from UTC (e.g., 420 for UTC+7, -300 for UTC-5)
301    ///   Pass `None` to reset to UTC (no offset)
302    ///
303    /// # Example
304    ///
305    /// ```ignore
306    /// let mut eval = JSONEval::new(schema, None, None)?;
307    ///
308    /// // Set to UTC+7 (Jakarta, Bangkok)
309    /// eval.set_timezone_offset(Some(420));
310    ///
311    /// // Reset to UTC
312    /// eval.set_timezone_offset(None);
313    /// ```
314    pub fn set_timezone_offset(&mut self, offset_minutes: Option<i32>) {
315        // Create new config with the timezone offset
316        let mut config = RLogicConfig::default();
317        if let Some(offset) = offset_minutes {
318            config = config.with_timezone_offset(offset);
319        }
320
321        // Recreate the engine with the new configuration
322        // This is necessary because RLogic is wrapped in Arc and config is part of the evaluator
323        self.engine = Arc::new(RLogic::with_config(config));
324
325        let _ = parse_schema::legacy::parse_schema(self);
326    }
327
328    /// Reload schema from MessagePack-encoded bytes
329    ///
330    /// # Arguments
331    ///
332    /// * `schema_msgpack` - MessagePack-encoded schema bytes
333    /// * `context` - Optional context data JSON string
334    /// * `data` - Optional initial data JSON string
335    ///
336    /// # Returns
337    ///
338    /// A `Result` indicating success or an error message
339    pub fn reload_schema_msgpack(
340        &mut self,
341        schema_msgpack: &[u8],
342        context: Option<&str>,
343        data: Option<&str>,
344    ) -> Result<(), String> {
345        // Deserialize MessagePack to Value
346        let schema_val: Value = rmp_serde::from_slice(schema_msgpack)
347            .map_err(|e| format!("failed to deserialize MessagePack schema: {e}"))?;
348
349        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))?;
350        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))?;
351
352        self.schema = Arc::new(schema_val);
353        self.context = context.clone();
354        self.data = data.clone();
355        self.evaluated_schema = (*self.schema).clone();
356        self.engine = Arc::new(RLogic::new());
357        self.dependents_evaluations = Arc::new(IndexMap::new());
358        self.rules_evaluations = Arc::new(Vec::new());
359        self.fields_with_rules = Arc::new(Vec::new());
360        self.others_evaluations = Arc::new(Vec::new());
361        self.value_evaluations = Arc::new(Vec::new());
362        self.layout_paths = Arc::new(Vec::new());
363        self.options_templates = Arc::new(Vec::new());
364        self.subforms.clear();
365        parse_schema::legacy::parse_schema(self)?;
366
367        // Re-initialize eval_data
368        self.eval_data =
369            EvalData::with_schema_data_context(&self.evaluated_schema, &data, &context);
370
371        // Cache the MessagePack for future retrievals
372        self.cached_msgpack_schema = Some(schema_msgpack.to_vec());
373
374        Ok(())
375    }
376
377    /// Reload schema from a cached ParsedSchema
378    ///
379    /// This is the most efficient way to reload as it reuses pre-parsed schema compilation.
380    ///
381    /// # Arguments
382    ///
383    /// * `parsed` - Arc reference to a cached ParsedSchema
384    /// * `context` - Optional context data JSON string
385    /// * `data` - Optional initial data JSON string
386    ///
387    /// # Returns
388    ///
389    /// A `Result` indicating success or an error message
390    pub fn reload_schema_parsed(
391        &mut self,
392        parsed: Arc<ParsedSchema>,
393        context: Option<&str>,
394        data: Option<&str>,
395    ) -> Result<(), String> {
396        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))?;
397        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))?;
398
399        // Share all the pre-compiled data from ParsedSchema
400        self.schema = Arc::clone(&parsed.schema);
401        self.evaluations = parsed.evaluations.clone();
402        self.tables = parsed.tables.clone();
403        self.table_metadata = parsed.table_metadata.clone();
404        self.dependencies = parsed.dependencies.clone();
405        self.sorted_evaluations = parsed.sorted_evaluations.clone();
406        self.dependents_evaluations = parsed.dependents_evaluations.clone();
407        self.rules_evaluations = parsed.rules_evaluations.clone();
408        self.fields_with_rules = parsed.fields_with_rules.clone();
409        self.others_evaluations = parsed.others_evaluations.clone();
410        self.value_evaluations = parsed.value_evaluations.clone();
411        self.layout_paths = parsed.layout_paths.clone();
412        self.options_templates = parsed.options_templates.clone();
413
414        // Share the engine Arc (cheap pointer clone, not data clone)
415        self.engine = parsed.engine.clone();
416
417        // Convert Arc<ParsedSchema> subforms to Box<JSONEval> subforms
418        let mut subforms = IndexMap::new();
419        for (path, subform_parsed) in &parsed.subforms {
420            let subform_eval =
421                JSONEval::with_parsed_schema(subform_parsed.clone(), Some("{}"), None)?;
422            subforms.insert(path.clone(), Box::new(subform_eval));
423        }
424        self.subforms = subforms;
425
426        self.context = context.clone();
427        self.data = data.clone();
428        self.evaluated_schema = (*self.schema).clone();
429
430        // Re-initialize eval_data
431        self.eval_data =
432            EvalData::with_schema_data_context(&self.evaluated_schema, &data, &context);
433
434        // Clear MessagePack cache since we're loading from ParsedSchema
435        self.cached_msgpack_schema = None;
436
437        Ok(())
438    }
439
440    /// Reload schema from ParsedSchemaCache using a cache key
441    ///
442    /// This is the recommended way for cross-platform cached schema reloading.
443    ///
444    /// # Arguments
445    ///
446    /// * `cache_key` - Key to lookup in the global ParsedSchemaCache
447    /// * `context` - Optional context data JSON string
448    /// * `data` - Optional initial data JSON string
449    ///
450    /// # Returns
451    ///
452    /// A `Result` indicating success or an error message
453    pub fn reload_schema_from_cache(
454        &mut self,
455        cache_key: &str,
456        context: Option<&str>,
457        data: Option<&str>,
458    ) -> Result<(), String> {
459        // Get the cached ParsedSchema from global cache
460        let parsed = PARSED_SCHEMA_CACHE
461            .get(cache_key)
462            .ok_or_else(|| format!("Schema '{}' not found in cache", cache_key))?;
463
464        // Use reload_schema_parsed with the cached schema
465        self.reload_schema_parsed(parsed, context, data)
466    }
467}