json_eval_rs/
parsed_schema.rs

1//! Parsed Schema - Reusable parsing results for caching across multiple JSONEval instances
2//!
3//! This module separates the parsing results from the evaluation state, allowing
4//! schemas to be parsed once and reused across multiple evaluations with different data/context.
5
6use indexmap::{IndexMap, IndexSet};
7use serde_json::Value;
8use std::sync::Arc;
9use crate::{LogicId, RLogic, RLogicConfig, TableMetadata, DependentItem};
10
11/// Parsed schema containing all pre-compiled evaluation metadata.
12/// This structure is separate from JSONEval to enable caching and reuse.
13/// 
14/// # Caching Strategy
15/// 
16/// Wrap ParsedSchema in Arc for sharing across threads and caching:
17/// 
18/// ```ignore
19/// use std::sync::Arc;
20/// 
21/// // Parse once and wrap in Arc for caching
22/// let parsed = Arc::new(ParsedSchema::parse(schema_str)?);
23/// cache.insert(schema_key, parsed.clone());
24/// 
25/// // Reuse across multiple evaluations (Arc::clone is cheap)
26/// let eval1 = JSONEval::with_parsed_schema(parsed.clone(), Some(context1), Some(data1))?;
27/// let eval2 = JSONEval::with_parsed_schema(parsed.clone(), Some(context2), Some(data2))?;
28/// ```
29pub struct ParsedSchema {
30    /// The original schema Value (wrapped in Arc for efficient sharing)
31    pub schema: Arc<Value>,
32    
33    /// RLogic engine with all compiled logic expressions (wrapped in Arc for sharing)
34    /// Multiple JSONEval instances created from the same ParsedSchema will share this engine
35    pub engine: Arc<RLogic>,
36    
37    /// Map of evaluation keys to compiled logic IDs (wrapped in Arc for zero-copy sharing)
38    pub evaluations: Arc<IndexMap<String, LogicId>>,
39    
40    /// Table definitions (rows, datas, skip, clear) (wrapped in Arc for zero-copy sharing)
41    pub tables: Arc<IndexMap<String, Value>>,
42    
43    /// Pre-compiled table metadata (computed at parse time for zero-copy evaluation)
44    pub table_metadata: Arc<IndexMap<String, TableMetadata>>,
45    
46    /// Dependencies map (evaluation key -> set of dependency paths) (wrapped in Arc for zero-copy sharing)
47    pub dependencies: Arc<IndexMap<String, IndexSet<String>>>,
48    
49    /// Evaluations grouped into parallel-executable batches (wrapped in Arc for zero-copy sharing)
50    /// Each inner Vec contains evaluations that can run concurrently
51    pub sorted_evaluations: Arc<Vec<Vec<String>>>,
52    
53    /// Evaluations categorized for result handling (wrapped in Arc for zero-copy sharing)
54    /// Dependents: map from source field to list of dependent items
55    pub dependents_evaluations: Arc<IndexMap<String, Vec<DependentItem>>>,
56    
57    /// Rules: evaluations with "/rules/" in path (wrapped in Arc for zero-copy sharing)
58    pub rules_evaluations: Arc<Vec<String>>,
59    
60    /// Fields with rules: dotted paths of all fields that have rules (wrapped in Arc for zero-copy sharing)
61    pub fields_with_rules: Arc<Vec<String>>,
62    
63    /// Others: all other evaluations not in sorted_evaluations (wrapped in Arc for zero-copy sharing)
64    pub others_evaluations: Arc<Vec<String>>,
65    
66    /// Value: evaluations ending with ".value" in path (wrapped in Arc for zero-copy sharing)
67    pub value_evaluations: Arc<Vec<String>>,
68    
69    /// Cached layout paths (collected at parse time) (wrapped in Arc for zero-copy sharing)
70    pub layout_paths: Arc<Vec<String>>,
71    
72    /// Options URL templates (url_path, template_str, params_path) (wrapped in Arc for zero-copy sharing)
73    pub options_templates: Arc<Vec<(String, String, String)>>,
74    
75    /// Subforms: cached ParsedSchema instances for array fields with items
76    /// Key is the schema path (e.g., "#/riders"), value is Arc<ParsedSchema> for cheap cloning
77    /// This allows subforms to be shared across multiple JSONEval instances efficiently
78    pub subforms: IndexMap<String, Arc<ParsedSchema>>,
79}
80
81impl ParsedSchema {
82    /// Parse a schema string into a ParsedSchema structure
83    /// 
84    /// # Arguments
85    /// 
86    /// * `schema` - JSON schema string
87    /// 
88    /// # Returns
89    /// 
90    /// A Result containing the ParsedSchema or an error
91    pub fn parse(schema: &str) -> Result<Self, String> {
92        let schema_val: Value = serde_json::from_str(schema)
93            .map_err(|e| format!("Failed to parse schema JSON: {}", e))?;
94        Self::parse_value(schema_val)
95    }
96    
97    /// Parse a schema Value into a ParsedSchema structure
98    /// 
99    /// # Arguments
100    /// 
101    /// * `schema_val` - JSON schema Value
102    /// 
103    /// # Returns
104    /// 
105    /// A Result containing the ParsedSchema or an error
106    pub fn parse_value(schema_val: Value) -> Result<Self, String> {
107        let engine_config = RLogicConfig::default();
108        
109        let mut parsed = Self {
110            schema: Arc::new(schema_val),
111            engine: Arc::new(RLogic::with_config(engine_config)),
112            evaluations: Arc::new(IndexMap::new()),
113            tables: Arc::new(IndexMap::new()),
114            table_metadata: Arc::new(IndexMap::new()),
115            dependencies: Arc::new(IndexMap::new()),
116            sorted_evaluations: Arc::new(Vec::new()),
117            dependents_evaluations: Arc::new(IndexMap::new()),
118            rules_evaluations: Arc::new(Vec::new()),
119            fields_with_rules: Arc::new(Vec::new()),
120            others_evaluations: Arc::new(Vec::new()),
121            value_evaluations: Arc::new(Vec::new()),
122            layout_paths: Arc::new(Vec::new()),
123            options_templates: Arc::new(Vec::new()),
124            subforms: IndexMap::new(),
125        };
126        
127        // Parse the schema to populate all fields
128        crate::parse_schema::parsed::parse_schema_into(&mut parsed)?;
129        
130        Ok(parsed)
131    }
132    
133    /// Parse a MessagePack-encoded schema into a ParsedSchema structure
134    /// 
135    /// # Arguments
136    /// 
137    /// * `schema_msgpack` - MessagePack-encoded schema bytes
138    /// 
139    /// # Returns
140    /// 
141    /// A Result containing the ParsedSchema or an error
142    pub fn parse_msgpack(schema_msgpack: &[u8]) -> Result<Self, String> {
143        let schema_val: Value = rmp_serde::from_slice(schema_msgpack)
144            .map_err(|e| format!("Failed to deserialize MessagePack schema: {}", e))?;
145        
146        Self::parse_value(schema_val)
147            .map_err(|e| format!("Failed to parse schema: {}", e))
148    }
149    
150    /// Get a reference to the original schema
151    pub fn schema(&self) -> &Value {
152        &*self.schema
153    }
154}