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}