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