Skip to main content

json_eval_rs/jsoneval/
core.rs

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