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::jsoneval::parsed_schema::ParsedSchema;
5use crate::jsoneval::parsed_schema_cache::PARSED_SCHEMA_CACHE;
6use crate::parse_schema;
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
17impl Clone for JSONEval {
18    fn clone(&self) -> Self {
19        Self {
20            schema: Arc::clone(&self.schema),
21            engine: Arc::clone(&self.engine),
22            evaluations: self.evaluations.clone(),
23            tables: self.tables.clone(),
24            table_metadata: self.table_metadata.clone(),
25            dependencies: self.dependencies.clone(),
26            sorted_evaluations: self.sorted_evaluations.clone(),
27            dependents_evaluations: self.dependents_evaluations.clone(),
28            rules_evaluations: self.rules_evaluations.clone(),
29            fields_with_rules: self.fields_with_rules.clone(),
30            others_evaluations: self.others_evaluations.clone(),
31            value_evaluations: self.value_evaluations.clone(),
32            layout_paths: self.layout_paths.clone(),
33            root_layout_paths: self.root_layout_paths.clone(),
34            layout_field_refs: self.layout_field_refs.clone(),
35            options_templates: self.options_templates.clone(),
36            subforms: self.subforms.clone(),
37            reffed_by: self.reffed_by.clone(),
38            dep_formula_triggers: self.dep_formula_triggers.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: self.eval_cache.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            static_arrays: self.static_arrays.clone(),
49            regex_cache: RwLock::new(HashMap::new()),
50            layout_state: RwLock::new(crate::jsoneval::layout::LayoutResolutionState::default()),
51            validation_cache: RwLock::new(self.validation_cache.read().unwrap().clone()),
52        }
53    }
54}
55
56impl JSONEval {
57    pub fn new(
58        schema: &str,
59        context: Option<&str>,
60        data: Option<&str>,
61    ) -> Result<Self, serde_json::Error> {
62        time_block!("JSONEval::new() [total]", {
63            // Use serde_json for schema (needs arbitrary_precision) and SIMD for data (needs speed)
64            let mut schema_val: Value =
65                time_block!("  parse schema JSON", { serde_json::from_str(schema)? });
66            let context: Value = time_block!("  parse context JSON", {
67                json_parser::parse_json_str(context.unwrap_or("{}"))
68                    .map_err(serde_json::Error::custom)?
69            });
70            let data: Value = time_block!("  parse data JSON", {
71                json_parser::parse_json_str(data.unwrap_or("{}"))
72                    .map_err(serde_json::Error::custom)?
73            });
74
75            // Extract large static arrays
76            let static_arrays = if let Some(params) = schema_val
77                .get_mut("$params")
78                .and_then(|v| v.as_object_mut())
79            {
80                crate::jsoneval::static_arrays::extract_from_params(params)
81            } else {
82                IndexMap::new()
83            };
84            let static_arrays = Arc::new(static_arrays);
85            let evaluated_schema = schema_val.clone();
86
87            // Use default config: tracking enabled
88            let engine_config = RLogicConfig::default();
89            let engine = RLogic::with_config(engine_config);
90            engine.set_static_arrays(Arc::clone(&static_arrays));
91
92            let mut instance = time_block!("  create instance struct", {
93                Self {
94                    schema: Arc::new(schema_val),
95                    evaluations: Arc::new(IndexMap::new()),
96                    tables: Arc::new(IndexMap::new()),
97                    table_metadata: Arc::new(IndexMap::new()),
98                    dependencies: Arc::new(IndexMap::new()),
99                    sorted_evaluations: Arc::new(Vec::new()),
100                    dependents_evaluations: Arc::new(IndexMap::new()),
101                    rules_evaluations: Arc::new(Vec::new()),
102                    fields_with_rules: Arc::new(Vec::new()),
103                    others_evaluations: Arc::new(Vec::new()),
104                    value_evaluations: Arc::new(Vec::new()),
105                    layout_paths: Arc::new(Vec::new()),
106                    root_layout_paths: Arc::new(Vec::new()),
107                    layout_field_refs: Arc::new(indexmap::IndexSet::new()),
108                    options_templates: Arc::new(Vec::new()),
109                    subforms: IndexMap::new(),
110                    engine: Arc::new(engine),
111                    reffed_by: Arc::new(IndexMap::new()),
112                    dep_formula_triggers: Arc::new(IndexMap::new()),
113                    context: context.clone(),
114                    data: data.clone(),
115                    evaluated_schema: evaluated_schema.clone(),
116                    eval_data: EvalData::with_schema_data_context(
117                        &evaluated_schema,
118                        &data,
119                        &context,
120                    ),
121                    eval_cache: crate::jsoneval::eval_cache::EvalCache::new(),
122                    eval_lock: Mutex::new(()),
123                    cached_msgpack_schema: None,
124                    conditional_hidden_fields: Arc::new(Vec::new()),
125                    conditional_readonly_fields: Arc::new(Vec::new()),
126                    static_arrays,
127                    regex_cache: RwLock::new(HashMap::new()),
128                    layout_state: RwLock::new(crate::jsoneval::layout::LayoutResolutionState::default()),
129                    validation_cache: RwLock::new(crate::jsoneval::validation_cache::ValidationCache::default()),
130                }
131            });
132            time_block!("  parse_schema", {
133                parse_schema::legacy::parse_schema(&mut instance)
134                    .map_err(serde_json::Error::custom)?
135            });
136            Ok(instance)
137        })
138    }
139
140    /// Create a new JSONEval instance for a subform, avoiding string serialization
141    pub(crate) fn new_subform(
142        schema_val: Value,
143        context: Value,
144        static_arrays: Arc<IndexMap<String, Arc<Value>>>,
145    ) -> Result<Self, serde_json::Error> {
146        time_block!("JSONEval::new_subform() [total]", {
147            // Data is empty for a subform initially
148            let data = Value::Object(serde_json::Map::new());
149            let evaluated_schema = schema_val.clone();
150
151            // Use default config: tracking enabled
152            let engine_config = RLogicConfig::default();
153            let engine = RLogic::with_config(engine_config);
154            engine.set_static_arrays(Arc::clone(&static_arrays));
155
156            let mut instance = time_block!("  create instance struct", {
157                Self {
158                    schema: Arc::new(schema_val),
159                    evaluations: Arc::new(IndexMap::new()),
160                    tables: Arc::new(IndexMap::new()),
161                    table_metadata: Arc::new(IndexMap::new()),
162                    dependencies: Arc::new(IndexMap::new()),
163                    sorted_evaluations: Arc::new(Vec::new()),
164                    dependents_evaluations: Arc::new(IndexMap::new()),
165                    rules_evaluations: Arc::new(Vec::new()),
166                    fields_with_rules: Arc::new(Vec::new()),
167                    others_evaluations: Arc::new(Vec::new()),
168                    value_evaluations: Arc::new(Vec::new()),
169                    layout_paths: Arc::new(Vec::new()),
170                    root_layout_paths: Arc::new(Vec::new()),
171                    layout_field_refs: Arc::new(indexmap::IndexSet::new()),
172                    options_templates: Arc::new(Vec::new()),
173                    subforms: IndexMap::new(),
174                    engine: Arc::new(engine),
175                    reffed_by: Arc::new(IndexMap::new()),
176                    dep_formula_triggers: Arc::new(IndexMap::new()),
177                    context: context.clone(),
178                    data: data.clone(),
179                    evaluated_schema: evaluated_schema.clone(),
180                    eval_data: EvalData::with_schema_data_context(
181                        &evaluated_schema,
182                        &data,
183                        &context,
184                    ),
185                    eval_cache: crate::jsoneval::eval_cache::EvalCache::new(),
186                    eval_lock: Mutex::new(()),
187                    cached_msgpack_schema: None,
188                    conditional_hidden_fields: Arc::new(Vec::new()),
189                    conditional_readonly_fields: Arc::new(Vec::new()),
190                    static_arrays,
191                    regex_cache: RwLock::new(HashMap::new()),
192                    layout_state: RwLock::new(crate::jsoneval::layout::LayoutResolutionState::default()),
193                    validation_cache: RwLock::new(crate::jsoneval::validation_cache::ValidationCache::default()),
194                }
195            });
196            time_block!("  parse_schema", {
197                parse_schema::legacy::parse_schema(&mut instance)
198                    .map_err(serde_json::Error::custom)?
199            });
200            Ok(instance)
201        })
202    }
203
204    /// Create a new JSONEval instance from MessagePack-encoded schema
205    ///
206    /// # Arguments
207    ///
208    /// * `schema_msgpack` - MessagePack-encoded schema bytes
209    /// * `context` - Optional JSON context string
210    /// * `data` - Optional JSON data string
211    ///
212    /// # Returns
213    ///
214    /// A Result containing the JSONEval instance or an error
215    pub fn new_from_msgpack(
216        schema_msgpack: &[u8],
217        context: Option<&str>,
218        data: Option<&str>,
219    ) -> Result<Self, String> {
220        // Store original MessagePack bytes for zero-copy retrieval
221        let cached_msgpack = schema_msgpack.to_vec();
222
223        // Deserialize MessagePack schema to Value
224        let mut schema_val: Value = rmp_serde::from_slice(schema_msgpack)
225            .map_err(|e| format!("Failed to deserialize MessagePack schema: {}", e))?;
226
227        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))
228            .map_err(|e| format!("Failed to parse context: {}", e))?;
229        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))
230            .map_err(|e| format!("Failed to parse data: {}", e))?;
231
232        // Extract large static arrays
233        let static_arrays = if let Some(params) = schema_val
234            .get_mut("$params")
235            .and_then(|v| v.as_object_mut())
236        {
237            crate::jsoneval::static_arrays::extract_from_params(params)
238        } else {
239            IndexMap::new()
240        };
241        let static_arrays = Arc::new(static_arrays);
242        let evaluated_schema = schema_val.clone();
243
244        let engine_config = RLogicConfig::default();
245        let engine = RLogic::with_config(engine_config);
246        engine.set_static_arrays(Arc::clone(&static_arrays));
247
248        let mut instance = Self {
249            schema: Arc::new(schema_val),
250            evaluations: Arc::new(IndexMap::new()),
251            tables: Arc::new(IndexMap::new()),
252            table_metadata: Arc::new(IndexMap::new()),
253            dependencies: Arc::new(IndexMap::new()),
254            sorted_evaluations: Arc::new(Vec::new()),
255            dependents_evaluations: Arc::new(IndexMap::new()),
256            rules_evaluations: Arc::new(Vec::new()),
257            fields_with_rules: Arc::new(Vec::new()),
258            others_evaluations: Arc::new(Vec::new()),
259            value_evaluations: Arc::new(Vec::new()),
260            layout_paths: Arc::new(Vec::new()),
261            root_layout_paths: Arc::new(Vec::new()),
262            layout_field_refs: Arc::new(indexmap::IndexSet::new()),
263            options_templates: Arc::new(Vec::new()),
264            subforms: IndexMap::new(),
265            engine: Arc::new(engine),
266            reffed_by: Arc::new(IndexMap::new()),
267            dep_formula_triggers: Arc::new(IndexMap::new()),
268            context: context.clone(),
269            data: data.clone(),
270            evaluated_schema: evaluated_schema.clone(),
271            eval_data: EvalData::with_schema_data_context(&evaluated_schema, &data, &context),
272            eval_cache: crate::jsoneval::eval_cache::EvalCache::new(),
273            eval_lock: Mutex::new(()),
274            cached_msgpack_schema: Some(cached_msgpack),
275            conditional_hidden_fields: Arc::new(Vec::new()),
276            conditional_readonly_fields: Arc::new(Vec::new()),
277            static_arrays,
278            regex_cache: RwLock::new(HashMap::new()),
279            layout_state: RwLock::new(crate::jsoneval::layout::LayoutResolutionState::default()),
280            validation_cache: RwLock::new(crate::jsoneval::validation_cache::ValidationCache::default()),
281        };
282        parse_schema::legacy::parse_schema(&mut instance)?;
283        Ok(instance)
284    }
285
286    /// Create a new JSONEval instance from a pre-parsed ParsedSchema
287    ///
288    /// This enables schema caching: parse once, reuse across multiple evaluations with different data/context.
289    ///
290    /// # Arguments
291    ///
292    /// * `parsed` - Arc-wrapped pre-parsed schema (can be cloned and cached)
293    /// * `context` - Optional JSON context string
294    /// * `data` - Optional JSON data string
295    ///
296    /// # Returns
297    ///
298    /// A Result containing the JSONEval instance or an error
299    ///
300    /// # Example
301    ///
302    /// ```ignore
303    /// use std::sync::Arc;
304    ///
305    /// // Parse schema once and wrap in Arc for caching
306    /// let parsed = Arc::new(ParsedSchema::parse(schema_str)?);
307    /// cache.insert(schema_key, parsed.clone());
308    ///
309    /// // Reuse across multiple evaluations (Arc::clone is cheap)
310    /// let eval1 = JSONEval::with_parsed_schema(parsed.clone(), Some(context1), Some(data1))?;
311    /// let eval2 = JSONEval::with_parsed_schema(parsed.clone(), Some(context2), Some(data2))?;
312    /// ```
313    pub fn with_parsed_schema(
314        parsed: Arc<ParsedSchema>,
315        context: Option<&str>,
316        data: Option<&str>,
317    ) -> Result<Self, String> {
318        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))
319            .map_err(|e| format!("Failed to parse context: {}", e))?;
320        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))
321            .map_err(|e| format!("Failed to parse data: {}", e))?;
322
323        let evaluated_schema = parsed.schema.clone();
324
325        // Share the engine Arc (cheap pointer clone, not data clone)
326        // Multiple JSONEval instances created from the same ParsedSchema will share the compiled RLogic
327        let engine = parsed.engine.clone();
328
329        // Convert Arc<ParsedSchema> subforms to Box<JSONEval> subforms
330        // This is a one-time conversion when creating JSONEval from ParsedSchema
331        let mut subforms = IndexMap::new();
332        for (path, subform_parsed) in &parsed.subforms {
333            // Create JSONEval from the cached ParsedSchema
334            let subform_eval =
335                JSONEval::with_parsed_schema(subform_parsed.clone(), Some("{}"), None)?;
336            subforms.insert(path.clone(), Box::new(subform_eval));
337        }
338
339        let instance = Self {
340            schema: Arc::clone(&parsed.schema),
341            evaluations: Arc::clone(&parsed.evaluations),
342            tables: Arc::clone(&parsed.tables),
343            table_metadata: Arc::clone(&parsed.table_metadata),
344            dependencies: Arc::clone(&parsed.dependencies),
345            sorted_evaluations: Arc::clone(&parsed.sorted_evaluations),
346            dependents_evaluations: Arc::clone(&parsed.dependents_evaluations),
347            rules_evaluations: Arc::clone(&parsed.rules_evaluations),
348            fields_with_rules: Arc::clone(&parsed.fields_with_rules),
349            others_evaluations: Arc::clone(&parsed.others_evaluations),
350            value_evaluations: Arc::clone(&parsed.value_evaluations),
351            layout_paths: Arc::clone(&parsed.layout_paths),
352            root_layout_paths: Arc::clone(&parsed.root_layout_paths),
353            layout_field_refs: Arc::clone(&parsed.layout_field_refs),
354            options_templates: Arc::clone(&parsed.options_templates),
355            subforms,
356            engine,
357            reffed_by: Arc::clone(&parsed.reffed_by),
358            dep_formula_triggers: Arc::clone(&parsed.dep_formula_triggers),
359            context: context.clone(),
360            data: data.clone(),
361            evaluated_schema: (*evaluated_schema).clone(),
362            eval_data: EvalData::with_schema_data_context(&evaluated_schema, &data, &context),
363            eval_cache: crate::jsoneval::eval_cache::EvalCache::new(),
364            eval_lock: Mutex::new(()),
365            cached_msgpack_schema: None,
366            conditional_hidden_fields: Arc::clone(&parsed.conditional_hidden_fields),
367            conditional_readonly_fields: Arc::clone(&parsed.conditional_readonly_fields),
368            static_arrays: Arc::clone(&parsed.static_arrays),
369            regex_cache: RwLock::new(HashMap::new()),
370            layout_state: RwLock::new(crate::jsoneval::layout::LayoutResolutionState::default()),
371            validation_cache: RwLock::new(crate::jsoneval::validation_cache::ValidationCache::default()),
372        };
373        Ok(instance)
374    }
375
376    pub fn reload_schema(
377        &mut self,
378        schema: &str,
379        context: Option<&str>,
380        data: Option<&str>,
381    ) -> Result<(), String> {
382        // Use serde_json for schema (precision) and SIMD for data (speed)
383        let mut schema_val: Value =
384            serde_json::from_str(schema).map_err(|e| format!("failed to parse schema: {e}"))?;
385        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))?;
386        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))?;
387        self.context = context.clone();
388        self.data = data.clone();
389
390        let static_arrays = if let Some(params) = schema_val
391            .get_mut("$params")
392            .and_then(|v| v.as_object_mut())
393        {
394            crate::jsoneval::static_arrays::extract_from_params(params)
395        } else {
396            IndexMap::new()
397        };
398        let static_arrays = Arc::new(static_arrays);
399        self.static_arrays = Arc::clone(&static_arrays);
400        self.schema = Arc::new(schema_val);
401        self.evaluated_schema = (*self.schema).clone();
402
403        let engine = RLogic::new();
404        engine.set_static_arrays(static_arrays);
405        self.engine = Arc::new(engine);
406
407        self.evaluations = Arc::new(IndexMap::new());
408        self.tables = Arc::new(IndexMap::new());
409        self.table_metadata = Arc::new(IndexMap::new());
410        self.dependencies = Arc::new(IndexMap::new());
411        self.sorted_evaluations = Arc::new(Vec::new());
412        self.dependents_evaluations = Arc::new(IndexMap::new());
413        self.rules_evaluations = Arc::new(Vec::new());
414        self.fields_with_rules = Arc::new(Vec::new());
415        self.others_evaluations = Arc::new(Vec::new());
416        self.value_evaluations = Arc::new(Vec::new());
417        self.layout_paths = Arc::new(Vec::new());
418        self.root_layout_paths = Arc::new(Vec::new());
419        self.options_templates = Arc::new(Vec::new());
420        self.reffed_by = Arc::new(IndexMap::new());
421        self.dep_formula_triggers = Arc::new(IndexMap::new());
422        self.conditional_hidden_fields = Arc::new(Vec::new());
423        self.conditional_readonly_fields = Arc::new(Vec::new());
424        self.subforms.clear();
425        parse_schema::legacy::parse_schema(self)?;
426
427        // Re-initialize eval_data with new schema, data, and context
428        self.eval_data =
429            EvalData::with_schema_data_context(&self.evaluated_schema, &data, &context);
430        self.eval_cache.clear();
431        if let Ok(mut cache) = self.regex_cache.write() {
432            cache.clear();
433        }
434
435        // Clear MessagePack cache since schema has been mutated
436        self.cached_msgpack_schema = None;
437        self.invalidate_layout_cache();
438        self.invalidate_validation_cache();
439
440        Ok(())
441    }
442
443    /// Set the timezone offset for datetime operations (TODAY, NOW)
444    ///
445    /// This method updates the RLogic engine configuration with a new timezone offset.
446    /// The offset will be applied to all subsequent datetime evaluations.
447    ///
448    /// # Arguments
449    ///
450    /// * `offset_minutes` - Timezone offset in minutes from UTC (e.g., 420 for UTC+7, -300 for UTC-5)
451    ///   Pass `None` to reset to UTC (no offset)
452    ///
453    /// # Example
454    ///
455    /// ```ignore
456    /// let mut eval = JSONEval::new(schema, None, None)?;
457    ///
458    /// // Set to UTC+7 (Jakarta, Bangkok)
459    /// eval.set_timezone_offset(Some(420));
460    ///
461    /// // Reset to UTC
462    /// eval.set_timezone_offset(None);
463    /// ```
464    pub fn set_timezone_offset(&mut self, offset_minutes: Option<i32>) {
465        // Create new config with the timezone offset
466        let mut config = RLogicConfig::default();
467        if let Some(offset) = offset_minutes {
468            config = config.with_timezone_offset(offset);
469        }
470
471        // Recreate the engine with the new configuration
472        // This is necessary because RLogic is wrapped in Arc and config is part of the evaluator
473        let engine = RLogic::with_config(config);
474        engine.set_static_arrays(Arc::clone(&self.static_arrays));
475        self.engine = Arc::new(engine);
476
477        let _ = parse_schema::legacy::parse_schema(self);
478    }
479
480    /// Reload schema from MessagePack-encoded bytes
481    ///
482    /// # Arguments
483    ///
484    /// * `schema_msgpack` - MessagePack-encoded schema bytes
485    /// * `context` - Optional context data JSON string
486    /// * `data` - Optional initial data JSON string
487    ///
488    /// # Returns
489    ///
490    /// A `Result` indicating success or an error message
491    pub fn reload_schema_msgpack(
492        &mut self,
493        schema_msgpack: &[u8],
494        context: Option<&str>,
495        data: Option<&str>,
496    ) -> Result<(), String> {
497        // Deserialize MessagePack to Value
498        let mut schema_val: Value = rmp_serde::from_slice(schema_msgpack)
499            .map_err(|e| format!("failed to deserialize MessagePack schema: {e}"))?;
500
501        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))?;
502        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))?;
503
504        self.context = context.clone();
505        self.data = data.clone();
506
507        let static_arrays = if let Some(params) = schema_val
508            .get_mut("$params")
509            .and_then(|v| v.as_object_mut())
510        {
511            crate::jsoneval::static_arrays::extract_from_params(params)
512        } else {
513            IndexMap::new()
514        };
515        let static_arrays = Arc::new(static_arrays);
516        self.static_arrays = Arc::clone(&static_arrays);
517        self.schema = Arc::new(schema_val);
518        self.evaluated_schema = (*self.schema).clone();
519
520        let engine = RLogic::new();
521        engine.set_static_arrays(static_arrays);
522        self.engine = Arc::new(engine);
523        self.evaluations = Arc::new(IndexMap::new());
524        self.tables = Arc::new(IndexMap::new());
525        self.table_metadata = Arc::new(IndexMap::new());
526        self.dependencies = Arc::new(IndexMap::new());
527        self.sorted_evaluations = Arc::new(Vec::new());
528        self.dependents_evaluations = Arc::new(IndexMap::new());
529        self.rules_evaluations = Arc::new(Vec::new());
530        self.fields_with_rules = Arc::new(Vec::new());
531        self.others_evaluations = Arc::new(Vec::new());
532        self.value_evaluations = Arc::new(Vec::new());
533        self.layout_paths = Arc::new(Vec::new());
534        self.root_layout_paths = Arc::new(Vec::new());
535        self.options_templates = Arc::new(Vec::new());
536        self.reffed_by = Arc::new(IndexMap::new());
537        self.dep_formula_triggers = Arc::new(IndexMap::new());
538        self.conditional_hidden_fields = Arc::new(Vec::new());
539        self.conditional_readonly_fields = Arc::new(Vec::new());
540        self.subforms.clear();
541        parse_schema::legacy::parse_schema(self)?;
542
543        // Re-initialize eval_data
544        self.eval_data =
545            EvalData::with_schema_data_context(&self.evaluated_schema, &data, &context);
546        self.eval_cache.clear();
547        if let Ok(mut cache) = self.regex_cache.write() {
548            cache.clear();
549        }
550
551        // Cache the MessagePack for future retrievals
552        self.cached_msgpack_schema = Some(schema_msgpack.to_vec());
553        self.invalidate_layout_cache();
554        self.invalidate_validation_cache();
555
556        Ok(())
557    }
558
559    /// Reload schema from a cached ParsedSchema
560    ///
561    /// This is the most efficient way to reload as it reuses pre-parsed schema compilation.
562    ///
563    /// # Arguments
564    ///
565    /// * `parsed` - Arc reference to a cached ParsedSchema
566    /// * `context` - Optional context data JSON string
567    /// * `data` - Optional initial data JSON string
568    ///
569    /// # Returns
570    ///
571    /// A `Result` indicating success or an error message
572    pub fn reload_schema_parsed(
573        &mut self,
574        parsed: Arc<ParsedSchema>,
575        context: Option<&str>,
576        data: Option<&str>,
577    ) -> Result<(), String> {
578        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))?;
579        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))?;
580
581        // Share all the pre-compiled data from ParsedSchema
582        self.schema = Arc::clone(&parsed.schema);
583        self.evaluations = parsed.evaluations.clone();
584        self.tables = parsed.tables.clone();
585        self.table_metadata = parsed.table_metadata.clone();
586        self.dependencies = parsed.dependencies.clone();
587        self.sorted_evaluations = parsed.sorted_evaluations.clone();
588        self.dependents_evaluations = parsed.dependents_evaluations.clone();
589        self.rules_evaluations = parsed.rules_evaluations.clone();
590        self.fields_with_rules = parsed.fields_with_rules.clone();
591        self.others_evaluations = parsed.others_evaluations.clone();
592        self.value_evaluations = parsed.value_evaluations.clone();
593        self.layout_paths = parsed.layout_paths.clone();
594        self.root_layout_paths = parsed.root_layout_paths.clone();
595        self.layout_field_refs = parsed.layout_field_refs.clone();
596        self.options_templates = parsed.options_templates.clone();
597        self.reffed_by = parsed.reffed_by.clone();
598        self.dep_formula_triggers = parsed.dep_formula_triggers.clone();
599        self.conditional_hidden_fields = parsed.conditional_hidden_fields.clone();
600        self.conditional_readonly_fields = parsed.conditional_readonly_fields.clone();
601        self.static_arrays = parsed.static_arrays.clone();
602
603        // Share the engine Arc (cheap pointer clone, not data clone)
604        self.engine = parsed.engine.clone();
605
606        // Convert Arc<ParsedSchema> subforms to Box<JSONEval> subforms
607        let mut subforms = IndexMap::new();
608        for (path, subform_parsed) in &parsed.subforms {
609            let subform_eval =
610                JSONEval::with_parsed_schema(subform_parsed.clone(), Some("{}"), None)?;
611            subforms.insert(path.clone(), Box::new(subform_eval));
612        }
613        self.subforms = subforms;
614
615        self.context = context.clone();
616        self.data = data.clone();
617        self.evaluated_schema = (*self.schema).clone();
618
619        // Re-initialize eval_data
620        self.eval_data =
621            EvalData::with_schema_data_context(&self.evaluated_schema, &data, &context);
622        self.eval_cache.clear();
623        self.engine.clear_indices();
624        if let Ok(mut cache) = self.regex_cache.write() {
625            cache.clear();
626        }
627
628        // Clear MessagePack cache since we're loading from ParsedSchema
629        self.cached_msgpack_schema = None;
630        self.invalidate_layout_cache();
631        self.invalidate_validation_cache();
632
633        Ok(())
634    }
635
636    /// Reload schema from ParsedSchemaCache using a cache key
637    ///
638    /// This is the recommended way for cross-platform cached schema reloading.
639    ///
640    /// # Arguments
641    ///
642    /// * `cache_key` - Key to lookup in the global ParsedSchemaCache
643    /// * `context` - Optional context data JSON string
644    /// * `data` - Optional initial data JSON string
645    ///
646    /// # Returns
647    ///
648    /// A `Result` indicating success or an error message
649    pub fn reload_schema_from_cache(
650        &mut self,
651        cache_key: &str,
652        context: Option<&str>,
653        data: Option<&str>,
654    ) -> Result<(), String> {
655        // Get the cached ParsedSchema from global cache
656        let parsed = PARSED_SCHEMA_CACHE
657            .get(cache_key)
658            .ok_or_else(|| format!("Schema '{}' not found in cache", cache_key))?;
659
660        // Use reload_schema_parsed with the cached schema
661        self.reload_schema_parsed(parsed, context, data)
662    }
663}