pub struct JSONEval {Show 25 fields
pub schema: Arc<Value>,
pub engine: Arc<RLogic>,
pub evaluations: Arc<IndexMap<String, LogicId>>,
pub tables: Arc<IndexMap<String, Value>>,
pub table_metadata: Arc<IndexMap<String, TableMetadata>>,
pub dependencies: Arc<IndexMap<String, IndexSet<String>>>,
pub sorted_evaluations: Arc<Vec<Vec<String>>>,
pub dependents_evaluations: Arc<IndexMap<String, Vec<DependentItem>>>,
pub rules_evaluations: Arc<Vec<String>>,
pub fields_with_rules: Arc<Vec<String>>,
pub others_evaluations: Arc<Vec<String>>,
pub value_evaluations: Arc<Vec<String>>,
pub layout_paths: Arc<Vec<String>>,
pub options_templates: Arc<Vec<(String, String, String)>>,
pub subforms: IndexMap<String, Box<JSONEval>>,
pub reffed_by: Arc<IndexMap<String, Vec<String>>>,
pub dep_formula_triggers: Arc<IndexMap<String, Vec<(String, usize)>>>,
pub conditional_hidden_fields: Arc<Vec<String>>,
pub conditional_readonly_fields: Arc<Vec<String>>,
pub static_arrays: Arc<IndexMap<String, Arc<Value>>>,
pub context: Value,
pub data: Value,
pub evaluated_schema: Value,
pub eval_data: EvalData,
pub eval_cache: EvalCache,
/* private fields */
}Fields§
§schema: Arc<Value>§engine: Arc<RLogic>§evaluations: Arc<IndexMap<String, LogicId>>§tables: Arc<IndexMap<String, Value>>§table_metadata: Arc<IndexMap<String, TableMetadata>>§dependencies: Arc<IndexMap<String, IndexSet<String>>>§sorted_evaluations: Arc<Vec<Vec<String>>>§dependents_evaluations: Arc<IndexMap<String, Vec<DependentItem>>>§rules_evaluations: Arc<Vec<String>>§fields_with_rules: Arc<Vec<String>>§others_evaluations: Arc<Vec<String>>§value_evaluations: Arc<Vec<String>>§layout_paths: Arc<Vec<String>>§options_templates: Arc<Vec<(String, String, String)>>§subforms: IndexMap<String, Box<JSONEval>>§reffed_by: Arc<IndexMap<String, Vec<String>>>§dep_formula_triggers: Arc<IndexMap<String, Vec<(String, usize)>>>Reverse map: data path → list of source field schema paths whose dependent value/clear formulas reference that path (excluding $value/$refValue context vars). When field X changes, source fields in dep_formula_triggers[X] are re-queued so their downstream dependents are re-evaluated with the new context.
conditional_readonly_fields: Arc<Vec<String>>§static_arrays: Arc<IndexMap<String, Arc<Value>>>§context: Value§data: Value§evaluated_schema: Value§eval_data: EvalData§eval_cache: EvalCacheImplementations§
Source§impl JSONEval
impl JSONEval
Sourcepub fn new(
schema: &str,
context: Option<&str>,
data: Option<&str>,
) -> Result<Self, Error>
pub fn new( schema: &str, context: Option<&str>, data: Option<&str>, ) -> Result<Self, Error>
Examples found in repository?
149fn demo_performance_comparison() -> Result<(), Box<dyn std::error::Error>> {
150 println!("⚡ Example 3: Performance Comparison");
151 println!("Comparing cached vs non-cached schema usage...\n");
152
153 let schema_json = r#"{
154 "$params": {
155 "value": { "type": "number" }
156 },
157 "doubled": {
158 "type": "number",
159 "$evaluation": { "*": [{"var": "$value"}, 2] }
160 },
161 "tripled": {
162 "type": "number",
163 "$evaluation": { "*": [{"var": "$value"}, 3] }
164 }
165 }"#;
166
167 let iterations = 100;
168
169 // WITHOUT CACHE: Parse schema every time
170 println!("🐌 Without cache (parse + evaluate each time):");
171 let start = Instant::now();
172 for i in 0..iterations {
173 let context = format!(r#"{{"value": {}}}"#, i);
174 let mut eval = JSONEval::new(schema_json, Some(&context), None)?;
175 eval.evaluate("{}", None, None, None)?;
176 }
177 let without_cache = start.elapsed();
178 println!(" Time: {:?}", without_cache);
179 println!(" Avg per iteration: {:?}\n", without_cache / iterations);
180
181 // WITH CACHE: Parse once, evaluate many times
182 println!("🚀 With cache (parse once, reuse for all evaluations):");
183 let cache = ParsedSchemaCache::new();
184
185 // Parse once
186 let parse_start = Instant::now();
187 let parsed = ParsedSchema::parse(schema_json)?;
188 cache.insert("perf-test".to_string(), Arc::new(parsed));
189 let parse_time = parse_start.elapsed();
190
191 // Evaluate many times
192 let eval_start = Instant::now();
193 for i in 0..iterations {
194 if let Some(cached) = cache.get("perf-test") {
195 let context = format!(r#"{{"value": {}}}"#, i);
196 let mut eval = JSONEval::with_parsed_schema(cached.clone(), Some(&context), None)?;
197 eval.evaluate("{}", None, None, None)?;
198 }
199 }
200 let eval_time = eval_start.elapsed();
201 let with_cache = parse_time + eval_time;
202
203 println!(" Parse time: {:?}", parse_time);
204 println!(" Eval time: {:?}", eval_time);
205 println!(" Total time: {:?}", with_cache);
206 println!(" Avg per iteration: {:?}\n", eval_time / iterations);
207
208 let speedup = without_cache.as_secs_f64() / with_cache.as_secs_f64();
209 println!("📈 Speedup: {:.2}x faster", speedup);
210
211 Ok(())
212}More examples
6fn main() {
7 println!("\n🚀 JSON Evaluation - SPAJ Toggle Example\n");
8
9 let schema_path = Path::new("samples/spaj.json");
10 let schema_str = fs::read_to_string(schema_path).expect("Failed to read schema");
11
12 // Initial data with minimal context required
13 let context_str = json!({
14 "agentProfile": { "sob": "AG" }
15 })
16 .to_string();
17
18 let initial_data = json!({
19 "illustration": {
20 "basicinformation": {
21 "print_polflag": false
22 }
23 }
24 })
25 .to_string();
26
27 // Initialize logic
28 let mut eval = JSONEval::new(&schema_str, Some(&context_str), Some(&initial_data))
29 .expect("Failed to create JSONEval");
30
31 // Helper to check visibility
32 let check_visibility = |eval: &mut JSONEval, expected_hidden: bool, step: &str| {
33 let result = eval.get_evaluated_schema(false);
34 let hidden = result.pointer("/illustration/properties/basicinformation/properties/print_poladdress/condition/hidden")
35 .and_then(|v| v.as_bool());
36
37 match hidden {
38 Some(val) => {
39 if val == expected_hidden {
40 println!(
41 "✅ {}: Hidden = {} (Expected: {})",
42 step, val, expected_hidden
43 );
44 } else {
45 println!(
46 "❌ {}: Hidden = {} (Expected: {})",
47 step, val, expected_hidden
48 );
49 }
50 }
51 None => println!("❌ {}: 'hidden' property not found", step),
52 }
53 };
54
55 // Step 1: Initial state (false)
56 println!("Step 1: Initial State (print_polflag: false)");
57 eval.evaluate(&initial_data, Some(&context_str), None, None)
58 .expect("Evaluation failed");
59 check_visibility(&mut eval, true, "Initial check");
60
61 // Step 2: Toggle to true
62 println!("\nStep 2: Toggle True (print_polflag: true)");
63 let data_true = json!({
64 "illustration": {
65 "basicinformation": {
66 "print_polflag": true
67 }
68 }
69 })
70 .to_string();
71 eval.evaluate(&data_true, Some(&context_str), None, None)
72 .expect("Evaluation failed");
73 check_visibility(&mut eval, false, "Toggle ON check");
74
75 // Step 3: Toggle back to false
76 println!("\nStep 3: Toggle False (print_polflag: false)");
77 let data_false = json!({
78 "illustration": {
79 "basicinformation": {
80 "print_polflag": false
81 }
82 }
83 })
84 .to_string();
85 eval.evaluate(&data_false, Some(&context_str), None, None)
86 .expect("Evaluation failed");
87
88 let hidden_path =
89 "#/illustration/properties/basicinformation/properties/print_poladdress/condition/hidden";
90 if let Some(deps) = eval.dependencies.get(hidden_path) {
91 println!("Debug: Dependencies for hidden: {:?}", deps);
92 } else {
93 println!("Debug: No dependencies found for hidden path");
94 }
95
96 // Debug: Print current flag value
97 if let Some(val) = eval
98 .get_evaluated_schema(false)
99 .pointer("/illustration/properties/basicinformation/properties/print_polflag/value")
100 {
101 println!("Debug: print_polflag value is: {}", val);
102 }
103
104 check_visibility(&mut eval, true, "Toggle OFF check");
105}37fn main() {
38 let args: Vec<String> = std::env::args().collect();
39 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("basic");
40
41 let mut scenario_filter: Option<String> = None;
42 let mut enable_comparison = false;
43 let mut show_timing = false;
44 let mut i = 1;
45
46 // Parse arguments
47 while i < args.len() {
48 let arg = &args[i];
49
50 if arg == "-h" || arg == "--help" {
51 print_help(program_name);
52 return;
53 } else if arg == "--compare" {
54 enable_comparison = true;
55 } else if arg == "--timing" {
56 show_timing = true;
57 } else if !arg.starts_with('-') {
58 scenario_filter = Some(arg.clone());
59 } else {
60 eprintln!("Error: unknown option '{}'", arg);
61 print_help(program_name);
62 return;
63 }
64
65 i += 1;
66 }
67
68 println!("\n🚀 JSON Evaluation - Basic Example (JSON/MsgPack Schema)\n");
69
70 if enable_comparison {
71 println!("🔍 Comparison: enabled");
72 }
73 if show_timing {
74 println!("⏱️ Internal timing: enabled");
75 }
76 if enable_comparison || show_timing {
77 println!();
78 }
79
80 let samples_dir = Path::new("samples");
81 let mut scenarios = common::discover_scenarios(samples_dir);
82
83 // Filter scenarios if a filter is provided
84 if let Some(ref filter) = scenario_filter {
85 scenarios.retain(|s| s.name.contains(filter));
86 println!("📋 Filtering scenarios matching: '{}'\n", filter);
87 }
88
89 if scenarios.is_empty() {
90 if let Some(filter) = scenario_filter {
91 println!(
92 "ℹ️ No scenarios found matching '{}' in `{}`.",
93 filter,
94 samples_dir.display()
95 );
96 } else {
97 println!(
98 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
99 samples_dir.display()
100 );
101 }
102 return;
103 }
104
105 println!("📊 Found {} scenario(s)\n", scenarios.len());
106
107 let mut total_parse_time = std::time::Duration::ZERO;
108 let mut total_eval_time = std::time::Duration::ZERO;
109 let mut successful_scenarios = 0;
110 let mut comparison_failures = 0;
111
112 for scenario in &scenarios {
113 println!("==============================");
114 println!("Scenario: {}", scenario.name);
115 println!(
116 "Schema: {} ({})",
117 scenario.schema_path.display(),
118 if scenario.is_msgpack {
119 "MessagePack"
120 } else {
121 "JSON"
122 }
123 );
124 println!("Data: {}\n", scenario.data_path.display());
125
126 // Clear timing data from previous scenarios
127 if show_timing {
128 json_eval_rs::enable_timing();
129 json_eval_rs::clear_timing_data();
130 }
131
132 let data_str = fs::read_to_string(&scenario.data_path)
133 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
134
135 // Step 1: Parse schema
136 let parse_start = Instant::now();
137
138 let mut eval = if scenario.is_msgpack {
139 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
140 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
141 });
142 println!(
143 " 📦 MessagePack schema size: {} bytes",
144 schema_msgpack.len()
145 );
146 JSONEval::new_from_msgpack(&schema_msgpack, None, Some(&data_str))
147 .unwrap_or_else(|e| panic!("failed to create JSONEval from MessagePack: {}", e))
148 } else {
149 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
150 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
151 });
152 JSONEval::new(&schema_str, None, Some(&data_str))
153 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
154 };
155
156 let parse_time = parse_start.elapsed();
157 println!(" 📝 Parse (new): {:?}", parse_time);
158
159 // Step 2: Evaluate
160 let eval_start = Instant::now();
161
162 eval.evaluate(&data_str, Some("{}"), None, None)
163 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
164
165 // Step 3: Validate
166 let validation_start = Instant::now();
167 let validation_result = eval
168 .validate(&data_str, None, None, None)
169 .unwrap_or_else(|e| panic!("validation failed: {}", e));
170 let validation_time = validation_start.elapsed();
171 println!(" 🛡️ Validate: {:?}", validation_time);
172
173 // Legacy behavior: get_evaluated_schema takes skip_layout: bool
174 // We pass false to ensure layout IS resolved
175 let evaluated_schema = eval.get_evaluated_schema(false);
176 let schema_value = eval.get_schema_value();
177 let eval_time = eval_start.elapsed();
178
179 println!(" ⚡ Eval: {:?}", eval_time);
180 println!(" ⏱️ Total: {:?}\n", parse_time + eval_time);
181
182 // Print detailed timing breakdown if --timing flag is set
183 if show_timing {
184 json_eval_rs::print_timing_summary();
185 }
186
187 total_parse_time += parse_time;
188 total_eval_time += eval_time;
189 successful_scenarios += 1;
190
191 // Save results
192 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
193 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
194 let value_path = samples_dir.join(format!("{}-schema-value.json", scenario.name));
195 let validation_path = samples_dir.join(format!("{}-validation.json", scenario.name));
196
197 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
198 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
199
200 let mut metadata_obj = Map::new();
201 metadata_obj.insert(
202 "dependencies".to_string(),
203 serde_json::to_value(&*eval.dependencies).unwrap(),
204 );
205 metadata_obj.insert(
206 "evaluations".to_string(),
207 serde_json::to_value(&*eval.evaluations).unwrap(),
208 );
209 metadata_obj.insert(
210 "sorted_evaluations".to_string(),
211 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
212 );
213
214 fs::write(
215 &parsed_path,
216 common::pretty_json(&Value::Object(metadata_obj)),
217 )
218 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
219
220 fs::write(&value_path, common::pretty_json(&schema_value))
221 .unwrap_or_else(|e| panic!("failed to write {}: {}", value_path.display(), e));
222
223 let validation_value = serde_json::to_value(&validation_result)
224 .unwrap_or_else(|e| panic!("failed to serialize validation result: {}", e));
225 fs::write(&validation_path, common::pretty_json(&validation_value))
226 .unwrap_or_else(|e| panic!("failed to write {}: {}", validation_path.display(), e));
227
228 println!("✅ Results saved:");
229 println!(" - {}", evaluated_path.display());
230 println!(" - {}", parsed_path.display());
231 println!(" - {}", value_path.display());
232 println!(" - {}\n", validation_path.display());
233
234 // Optional comparison
235 if enable_comparison {
236 if let Some(comp_path) = &scenario.comparison_path {
237 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
238 comparison_failures += 1;
239 }
240 println!();
241 }
242 }
243 }
244
245 // Print summary
246 println!("{}", "=".repeat(50));
247 println!("📊 Summary");
248 println!("{}", "=".repeat(50));
249 println!("Total scenarios run: {}", successful_scenarios);
250 println!("Total parse time: {:?}", total_parse_time);
251 println!("Total eval time: {:?}", total_eval_time);
252 println!("Total time: {:?}", total_parse_time + total_eval_time);
253
254 if successful_scenarios > 1 {
255 println!("\nAverage per scenario:");
256 println!(
257 " Parse: {:?}",
258 total_parse_time / successful_scenarios as u32
259 );
260 println!(
261 " Eval: {:?}",
262 total_eval_time / successful_scenarios as u32
263 );
264 }
265
266 if enable_comparison {
267 println!("Comparison failures: {}", comparison_failures);
268 }
269
270 println!("\n✅ All scenarios completed!\n");
271}44fn main() {
45 let args: Vec<String> = std::env::args().collect();
46 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("benchmark");
47
48 let mut iterations = 1usize;
49 let mut scenario_filter: Option<String> = None;
50 let mut show_cpu_info = false;
51 let mut use_parsed_schema = false;
52 let mut use_cache = false;
53 let mut concurrent_count: Option<usize> = None;
54 let mut enable_comparison = false;
55 let mut show_timing = false;
56 let mut i = 1;
57
58 // Parse arguments
59 while i < args.len() {
60 let arg = &args[i];
61
62 if arg == "-h" || arg == "--help" {
63 print_help(program_name);
64 return;
65 } else if arg == "--cpu-info" {
66 show_cpu_info = true;
67 } else if arg == "--parsed" {
68 use_parsed_schema = true;
69 } else if arg == "--cache" {
70 use_cache = true;
71 } else if arg == "--compare" {
72 enable_comparison = true;
73 } else if arg == "--timing" {
74 show_timing = true;
75 } else if arg == "--concurrent" {
76 if i + 1 >= args.len() {
77 eprintln!("Error: {} requires a value", arg);
78 print_help(program_name);
79 return;
80 }
81 i += 1;
82 match args[i].parse::<usize>() {
83 Ok(n) if n > 0 => concurrent_count = Some(n),
84 _ => {
85 eprintln!(
86 "Error: concurrent count must be a positive integer, got '{}'",
87 args[i]
88 );
89 return;
90 }
91 }
92 } else if arg == "-i" || arg == "--iterations" {
93 if i + 1 >= args.len() {
94 eprintln!("Error: {} requires a value", arg);
95 print_help(program_name);
96 return;
97 }
98 i += 1;
99 match args[i].parse::<usize>() {
100 Ok(n) if n > 0 => iterations = n,
101 _ => {
102 eprintln!(
103 "Error: iterations must be a positive integer, got '{}'",
104 args[i]
105 );
106 return;
107 }
108 }
109 } else if !arg.starts_with('-') {
110 scenario_filter = Some(arg.clone());
111 } else {
112 eprintln!("Error: unknown option '{}'", arg);
113 print_help(program_name);
114 return;
115 }
116
117 i += 1;
118 }
119
120 println!("\n🚀 JSON Evaluation - Benchmark\n");
121
122 // Show CPU info if requested or if running benchmarks
123 if show_cpu_info || iterations > 1 || concurrent_count.is_some() {
124 common::print_cpu_info();
125 }
126
127 if use_parsed_schema {
128 println!("📦 Mode: ParsedSchema (parse once, reuse for all iterations)\n");
129 }
130
131 if use_cache {
132 println!("♻️ Mode: Cache (reuse JSONEval instance across iterations)\n");
133 }
134
135 if let Some(count) = concurrent_count {
136 println!("🔀 Concurrent evaluations: {} threads\n", count);
137 } else if iterations > 1 {
138 println!("🔄 Iterations per scenario: {}\n", iterations);
139 }
140
141 if enable_comparison {
142 println!("🔍 Comparison: enabled");
143 }
144 if show_timing {
145 println!("⏱️ Internal timing: enabled");
146 }
147 if enable_comparison || show_timing {
148 println!();
149 }
150
151 let samples_dir = Path::new("samples");
152 let mut scenarios = common::discover_scenarios(samples_dir);
153
154 // Filter scenarios if a filter is provided
155 if let Some(ref filter) = scenario_filter {
156 scenarios.retain(|s| s.name.contains(filter));
157 println!("📋 Filtering scenarios matching: '{}'\n", filter);
158 }
159
160 if scenarios.is_empty() {
161 if let Some(filter) = scenario_filter {
162 println!(
163 "ℹ️ No scenarios found matching '{}' in `{}`.",
164 filter,
165 samples_dir.display()
166 );
167 } else {
168 println!(
169 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
170 samples_dir.display()
171 );
172 }
173 return;
174 }
175
176 println!("📊 Found {} scenario(s)\n", scenarios.len());
177
178 let mut total_parse_time = std::time::Duration::ZERO;
179 let mut total_eval_time = std::time::Duration::ZERO;
180 let mut successful_scenarios = 0;
181 let mut comparison_failures = 0;
182
183 for scenario in &scenarios {
184 println!("==============================");
185 println!("Scenario: {}", scenario.name);
186 println!(
187 "Schema: {} ({})",
188 scenario.schema_path.display(),
189 if scenario.is_msgpack {
190 "MessagePack"
191 } else {
192 "JSON"
193 }
194 );
195 println!("Data: {}\n", scenario.data_path.display());
196
197 // Clear timing data from previous scenarios
198 if show_timing {
199 json_eval_rs::enable_timing();
200 json_eval_rs::clear_timing_data();
201 }
202
203 let data_str = fs::read_to_string(&scenario.data_path)
204 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
205
206 println!("Running evaluation...\n");
207
208 let (parse_time, eval_time, evaluated_schema, eval, iteration_times) = if use_parsed_schema
209 {
210 // ParsedSchema mode: parse once, reuse for all iterations/threads
211 let start_time = Instant::now();
212
213 let parsed_schema = if scenario.is_msgpack {
214 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
215 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
216 });
217 println!(
218 " 📦 MessagePack schema size: {} bytes",
219 schema_msgpack.len()
220 );
221 Arc::new(
222 ParsedSchema::parse_msgpack(&schema_msgpack)
223 .unwrap_or_else(|e| panic!("failed to parse MessagePack schema: {}", e)),
224 )
225 } else {
226 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
227 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
228 });
229 Arc::new(
230 ParsedSchema::parse(&schema_str)
231 .unwrap_or_else(|e| panic!("failed to parse schema: {}", e)),
232 )
233 };
234
235 let parse_time = start_time.elapsed();
236 println!(" Schema parsing & compilation: {:?}", parse_time);
237
238 // Concurrent mode with ParsedSchema
239 if let Some(thread_count) = concurrent_count {
240 use std::thread;
241
242 let eval_start = Instant::now();
243 let mut handles = vec![];
244
245 for thread_id in 0..thread_count {
246 let parsed_clone = parsed_schema.clone();
247 let data_str_clone = data_str.clone();
248 let iter_count = iterations;
249 let thread_use_cache = use_cache;
250
251 let handle = thread::spawn(move || {
252 let mut thread_times = Vec::with_capacity(iter_count);
253 let mut last_schema = Value::Null;
254
255 let mut eval_instance = JSONEval::with_parsed_schema(
256 parsed_clone.clone(),
257 Some("{}"),
258 Some(&data_str_clone),
259 )
260 .unwrap();
261
262 for iter in 0..iter_count {
263 let iter_start = Instant::now();
264
265 if !thread_use_cache && iter > 0 {
266 eval_instance = JSONEval::with_parsed_schema(
267 parsed_clone.clone(),
268 Some("{}"),
269 Some(&data_str_clone),
270 )
271 .unwrap();
272 }
273
274 eval_instance
275 .evaluate(&data_str_clone, Some("{}"), None, None)
276 .unwrap();
277 last_schema = eval_instance.get_evaluated_schema(false);
278 thread_times.push(iter_start.elapsed());
279 }
280
281 (thread_times, last_schema, thread_id)
282 });
283 handles.push(handle);
284 }
285
286 let mut all_iteration_times = Vec::new();
287 let mut evaluated_schema = Value::Null;
288
289 for handle in handles {
290 let (thread_times, thread_schema, thread_id) = handle.join().unwrap();
291 println!(
292 " Thread {} completed {} iterations",
293 thread_id,
294 thread_times.len()
295 );
296 all_iteration_times.extend(thread_times);
297 evaluated_schema = thread_schema; // Use last thread's result
298 }
299
300 let eval_time = eval_start.elapsed();
301
302 // Create a temp eval for metadata export
303 let temp_eval = JSONEval::with_parsed_schema(
304 parsed_schema.clone(),
305 Some("{}"),
306 Some(&data_str),
307 )
308 .unwrap();
309
310 (
311 parse_time,
312 eval_time,
313 evaluated_schema,
314 temp_eval,
315 all_iteration_times,
316 )
317 } else {
318 // Sequential iterations with ParsedSchema
319 let eval_start = Instant::now();
320 let mut evaluated_schema = Value::Null;
321 let mut iteration_times = Vec::with_capacity(iterations);
322 let mut eval_instance = JSONEval::with_parsed_schema(
323 parsed_schema.clone(),
324 Some("{}"),
325 Some(&data_str),
326 )
327 .unwrap();
328
329 for iter in 0..iterations {
330 let iter_start = Instant::now();
331
332 if !use_cache && iter > 0 {
333 eval_instance = JSONEval::with_parsed_schema(
334 parsed_schema.clone(),
335 Some("{}"),
336 Some(&data_str),
337 )
338 .unwrap();
339 }
340
341 eval_instance
342 .evaluate(&data_str, Some("{}"), None, None)
343 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
344 evaluated_schema = eval_instance.get_evaluated_schema(false);
345 iteration_times.push(iter_start.elapsed());
346
347 if iterations > 1 && (iter + 1) % 10 == 0 {
348 print!(".");
349 if (iter + 1) % 50 == 0 {
350 println!(" {}/{}", iter + 1, iterations);
351 }
352 }
353 }
354
355 if iterations > 1 && iterations % 50 != 0 {
356 println!(" {}/{}", iterations, iterations);
357 }
358
359 let eval_time = eval_start.elapsed();
360 (
361 parse_time,
362 eval_time,
363 evaluated_schema,
364 eval_instance,
365 iteration_times,
366 )
367 }
368 } else {
369 // Traditional mode: parse and create JSONEval each time
370 let schema_msgpack = if scenario.is_msgpack {
371 let bytes = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
372 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
373 });
374 println!(" 📦 MessagePack schema size: {} bytes", bytes.len());
375 Some(bytes)
376 } else {
377 None
378 };
379
380 let schema_str = if !scenario.is_msgpack {
381 Some(
382 fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
383 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
384 }),
385 )
386 } else {
387 None
388 };
389
390 let start_time = Instant::now();
391 let mut eval = if scenario.is_msgpack {
392 JSONEval::new_from_msgpack(schema_msgpack.as_ref().unwrap(), None, Some(&data_str))
393 .unwrap_or_else(|e| panic!("failed to create JSONEval from MessagePack: {}", e))
394 } else {
395 JSONEval::new(schema_str.as_ref().unwrap(), None, Some(&data_str))
396 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
397 };
398 let parse_time = start_time.elapsed();
399 println!(" Schema parsing & compilation: {:?}", parse_time);
400
401 let eval_start = Instant::now();
402 let mut evaluated_schema = Value::Null;
403 let mut iteration_times = Vec::with_capacity(iterations);
404
405 for iter in 0..iterations {
406 let iter_start = Instant::now();
407
408 if !use_cache && iter > 0 {
409 eval = if scenario.is_msgpack {
410 JSONEval::new_from_msgpack(
411 schema_msgpack.as_ref().unwrap(),
412 None,
413 Some(&data_str),
414 )
415 .unwrap_or_else(|e| {
416 panic!("failed to create JSONEval from MessagePack: {}", e)
417 })
418 } else {
419 JSONEval::new(schema_str.as_ref().unwrap(), None, Some(&data_str))
420 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
421 };
422 }
423
424 eval.evaluate(&data_str, Some("{}"), None, None)
425 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
426 evaluated_schema = eval.get_evaluated_schema(false);
427 iteration_times.push(iter_start.elapsed());
428
429 if iterations > 1 && (iter + 1) % 10 == 0 {
430 print!(".");
431 if (iter + 1) % 50 == 0 {
432 println!(" {}/{}", iter + 1, iterations);
433 }
434 }
435 }
436
437 if iterations > 1 && iterations % 50 != 0 {
438 println!(" {}/{}", iterations, iterations);
439 }
440
441 let eval_time = eval_start.elapsed();
442 (
443 parse_time,
444 eval_time,
445 evaluated_schema,
446 eval,
447 iteration_times,
448 )
449 };
450
451 // Calculate statistics
452 let total_iterations = iteration_times.len();
453 if total_iterations == 1 {
454 println!(" Evaluation: {:?}", eval_time);
455 } else {
456 let avg_time = eval_time / total_iterations as u32;
457 let min_time = iteration_times.iter().min().unwrap();
458 let max_time = iteration_times.iter().max().unwrap();
459
460 println!(" Total evaluation time: {:?}", eval_time);
461 println!(" Total iterations: {}", total_iterations);
462 println!(" Average per iteration: {:?}", avg_time);
463 println!(" Min: {:?} | Max: {:?}", min_time, max_time);
464 }
465
466 let total_time = parse_time + eval_time;
467 println!("⏱️ Execution time: {:?}\n", total_time);
468
469 // Print detailed timing breakdown if --timing flag is set
470 if show_timing {
471 json_eval_rs::print_timing_summary();
472 }
473
474 // Track statistics
475 total_parse_time += parse_time;
476 total_eval_time += eval_time;
477 successful_scenarios += 1;
478
479 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
480 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
481
482 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
483 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
484
485 let mut metadata_obj = Map::new();
486 metadata_obj.insert(
487 "dependencies".to_string(),
488 serde_json::to_value(&*eval.dependencies).unwrap(),
489 );
490 metadata_obj.insert(
491 "sorted_evaluations".to_string(),
492 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
493 );
494
495 fs::write(
496 &parsed_path,
497 common::pretty_json(&Value::Object(metadata_obj)),
498 )
499 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
500
501 println!("✅ Results saved:");
502 println!(" - {}", evaluated_path.display());
503 println!(" - {}\n", parsed_path.display());
504
505 // Optional comparison
506 if enable_comparison {
507 if let Some(comp_path) = &scenario.comparison_path {
508 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
509 comparison_failures += 1;
510 }
511 println!();
512 }
513 }
514 }
515
516 // Print summary statistics
517 if successful_scenarios > 0 {
518 println!("\n{}", "=".repeat(50));
519 println!("📊 Summary Statistics");
520 println!("{}", "=".repeat(50));
521 println!("Total scenarios run: {}", successful_scenarios);
522 println!("Total parsing time: {:?}", total_parse_time);
523 println!("Total evaluation time: {:?}", total_eval_time);
524 println!("Total time: {:?}", total_parse_time + total_eval_time);
525
526 if successful_scenarios > 1 {
527 println!("\nAverage per scenario:");
528 println!(
529 " Parsing: {:?}",
530 total_parse_time / successful_scenarios as u32
531 );
532 println!(
533 " Evaluation: {:?}",
534 total_eval_time / successful_scenarios as u32
535 );
536 }
537
538 if enable_comparison {
539 println!("\nComparison failures: {}", comparison_failures);
540 }
541
542 println!("\n✅ All scenarios completed successfully!\n");
543 }
544}Sourcepub fn new_from_msgpack(
schema_msgpack: &[u8],
context: Option<&str>,
data: Option<&str>,
) -> Result<Self, String>
pub fn new_from_msgpack( schema_msgpack: &[u8], context: Option<&str>, data: Option<&str>, ) -> Result<Self, String>
Create a new JSONEval instance from MessagePack-encoded schema
§Arguments
schema_msgpack- MessagePack-encoded schema bytescontext- Optional JSON context stringdata- Optional JSON data string
§Returns
A Result containing the JSONEval instance or an error
Examples found in repository?
37fn main() {
38 let args: Vec<String> = std::env::args().collect();
39 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("basic");
40
41 let mut scenario_filter: Option<String> = None;
42 let mut enable_comparison = false;
43 let mut show_timing = false;
44 let mut i = 1;
45
46 // Parse arguments
47 while i < args.len() {
48 let arg = &args[i];
49
50 if arg == "-h" || arg == "--help" {
51 print_help(program_name);
52 return;
53 } else if arg == "--compare" {
54 enable_comparison = true;
55 } else if arg == "--timing" {
56 show_timing = true;
57 } else if !arg.starts_with('-') {
58 scenario_filter = Some(arg.clone());
59 } else {
60 eprintln!("Error: unknown option '{}'", arg);
61 print_help(program_name);
62 return;
63 }
64
65 i += 1;
66 }
67
68 println!("\n🚀 JSON Evaluation - Basic Example (JSON/MsgPack Schema)\n");
69
70 if enable_comparison {
71 println!("🔍 Comparison: enabled");
72 }
73 if show_timing {
74 println!("⏱️ Internal timing: enabled");
75 }
76 if enable_comparison || show_timing {
77 println!();
78 }
79
80 let samples_dir = Path::new("samples");
81 let mut scenarios = common::discover_scenarios(samples_dir);
82
83 // Filter scenarios if a filter is provided
84 if let Some(ref filter) = scenario_filter {
85 scenarios.retain(|s| s.name.contains(filter));
86 println!("📋 Filtering scenarios matching: '{}'\n", filter);
87 }
88
89 if scenarios.is_empty() {
90 if let Some(filter) = scenario_filter {
91 println!(
92 "ℹ️ No scenarios found matching '{}' in `{}`.",
93 filter,
94 samples_dir.display()
95 );
96 } else {
97 println!(
98 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
99 samples_dir.display()
100 );
101 }
102 return;
103 }
104
105 println!("📊 Found {} scenario(s)\n", scenarios.len());
106
107 let mut total_parse_time = std::time::Duration::ZERO;
108 let mut total_eval_time = std::time::Duration::ZERO;
109 let mut successful_scenarios = 0;
110 let mut comparison_failures = 0;
111
112 for scenario in &scenarios {
113 println!("==============================");
114 println!("Scenario: {}", scenario.name);
115 println!(
116 "Schema: {} ({})",
117 scenario.schema_path.display(),
118 if scenario.is_msgpack {
119 "MessagePack"
120 } else {
121 "JSON"
122 }
123 );
124 println!("Data: {}\n", scenario.data_path.display());
125
126 // Clear timing data from previous scenarios
127 if show_timing {
128 json_eval_rs::enable_timing();
129 json_eval_rs::clear_timing_data();
130 }
131
132 let data_str = fs::read_to_string(&scenario.data_path)
133 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
134
135 // Step 1: Parse schema
136 let parse_start = Instant::now();
137
138 let mut eval = if scenario.is_msgpack {
139 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
140 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
141 });
142 println!(
143 " 📦 MessagePack schema size: {} bytes",
144 schema_msgpack.len()
145 );
146 JSONEval::new_from_msgpack(&schema_msgpack, None, Some(&data_str))
147 .unwrap_or_else(|e| panic!("failed to create JSONEval from MessagePack: {}", e))
148 } else {
149 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
150 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
151 });
152 JSONEval::new(&schema_str, None, Some(&data_str))
153 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
154 };
155
156 let parse_time = parse_start.elapsed();
157 println!(" 📝 Parse (new): {:?}", parse_time);
158
159 // Step 2: Evaluate
160 let eval_start = Instant::now();
161
162 eval.evaluate(&data_str, Some("{}"), None, None)
163 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
164
165 // Step 3: Validate
166 let validation_start = Instant::now();
167 let validation_result = eval
168 .validate(&data_str, None, None, None)
169 .unwrap_or_else(|e| panic!("validation failed: {}", e));
170 let validation_time = validation_start.elapsed();
171 println!(" 🛡️ Validate: {:?}", validation_time);
172
173 // Legacy behavior: get_evaluated_schema takes skip_layout: bool
174 // We pass false to ensure layout IS resolved
175 let evaluated_schema = eval.get_evaluated_schema(false);
176 let schema_value = eval.get_schema_value();
177 let eval_time = eval_start.elapsed();
178
179 println!(" ⚡ Eval: {:?}", eval_time);
180 println!(" ⏱️ Total: {:?}\n", parse_time + eval_time);
181
182 // Print detailed timing breakdown if --timing flag is set
183 if show_timing {
184 json_eval_rs::print_timing_summary();
185 }
186
187 total_parse_time += parse_time;
188 total_eval_time += eval_time;
189 successful_scenarios += 1;
190
191 // Save results
192 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
193 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
194 let value_path = samples_dir.join(format!("{}-schema-value.json", scenario.name));
195 let validation_path = samples_dir.join(format!("{}-validation.json", scenario.name));
196
197 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
198 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
199
200 let mut metadata_obj = Map::new();
201 metadata_obj.insert(
202 "dependencies".to_string(),
203 serde_json::to_value(&*eval.dependencies).unwrap(),
204 );
205 metadata_obj.insert(
206 "evaluations".to_string(),
207 serde_json::to_value(&*eval.evaluations).unwrap(),
208 );
209 metadata_obj.insert(
210 "sorted_evaluations".to_string(),
211 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
212 );
213
214 fs::write(
215 &parsed_path,
216 common::pretty_json(&Value::Object(metadata_obj)),
217 )
218 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
219
220 fs::write(&value_path, common::pretty_json(&schema_value))
221 .unwrap_or_else(|e| panic!("failed to write {}: {}", value_path.display(), e));
222
223 let validation_value = serde_json::to_value(&validation_result)
224 .unwrap_or_else(|e| panic!("failed to serialize validation result: {}", e));
225 fs::write(&validation_path, common::pretty_json(&validation_value))
226 .unwrap_or_else(|e| panic!("failed to write {}: {}", validation_path.display(), e));
227
228 println!("✅ Results saved:");
229 println!(" - {}", evaluated_path.display());
230 println!(" - {}", parsed_path.display());
231 println!(" - {}", value_path.display());
232 println!(" - {}\n", validation_path.display());
233
234 // Optional comparison
235 if enable_comparison {
236 if let Some(comp_path) = &scenario.comparison_path {
237 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
238 comparison_failures += 1;
239 }
240 println!();
241 }
242 }
243 }
244
245 // Print summary
246 println!("{}", "=".repeat(50));
247 println!("📊 Summary");
248 println!("{}", "=".repeat(50));
249 println!("Total scenarios run: {}", successful_scenarios);
250 println!("Total parse time: {:?}", total_parse_time);
251 println!("Total eval time: {:?}", total_eval_time);
252 println!("Total time: {:?}", total_parse_time + total_eval_time);
253
254 if successful_scenarios > 1 {
255 println!("\nAverage per scenario:");
256 println!(
257 " Parse: {:?}",
258 total_parse_time / successful_scenarios as u32
259 );
260 println!(
261 " Eval: {:?}",
262 total_eval_time / successful_scenarios as u32
263 );
264 }
265
266 if enable_comparison {
267 println!("Comparison failures: {}", comparison_failures);
268 }
269
270 println!("\n✅ All scenarios completed!\n");
271}More examples
44fn main() {
45 let args: Vec<String> = std::env::args().collect();
46 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("benchmark");
47
48 let mut iterations = 1usize;
49 let mut scenario_filter: Option<String> = None;
50 let mut show_cpu_info = false;
51 let mut use_parsed_schema = false;
52 let mut use_cache = false;
53 let mut concurrent_count: Option<usize> = None;
54 let mut enable_comparison = false;
55 let mut show_timing = false;
56 let mut i = 1;
57
58 // Parse arguments
59 while i < args.len() {
60 let arg = &args[i];
61
62 if arg == "-h" || arg == "--help" {
63 print_help(program_name);
64 return;
65 } else if arg == "--cpu-info" {
66 show_cpu_info = true;
67 } else if arg == "--parsed" {
68 use_parsed_schema = true;
69 } else if arg == "--cache" {
70 use_cache = true;
71 } else if arg == "--compare" {
72 enable_comparison = true;
73 } else if arg == "--timing" {
74 show_timing = true;
75 } else if arg == "--concurrent" {
76 if i + 1 >= args.len() {
77 eprintln!("Error: {} requires a value", arg);
78 print_help(program_name);
79 return;
80 }
81 i += 1;
82 match args[i].parse::<usize>() {
83 Ok(n) if n > 0 => concurrent_count = Some(n),
84 _ => {
85 eprintln!(
86 "Error: concurrent count must be a positive integer, got '{}'",
87 args[i]
88 );
89 return;
90 }
91 }
92 } else if arg == "-i" || arg == "--iterations" {
93 if i + 1 >= args.len() {
94 eprintln!("Error: {} requires a value", arg);
95 print_help(program_name);
96 return;
97 }
98 i += 1;
99 match args[i].parse::<usize>() {
100 Ok(n) if n > 0 => iterations = n,
101 _ => {
102 eprintln!(
103 "Error: iterations must be a positive integer, got '{}'",
104 args[i]
105 );
106 return;
107 }
108 }
109 } else if !arg.starts_with('-') {
110 scenario_filter = Some(arg.clone());
111 } else {
112 eprintln!("Error: unknown option '{}'", arg);
113 print_help(program_name);
114 return;
115 }
116
117 i += 1;
118 }
119
120 println!("\n🚀 JSON Evaluation - Benchmark\n");
121
122 // Show CPU info if requested or if running benchmarks
123 if show_cpu_info || iterations > 1 || concurrent_count.is_some() {
124 common::print_cpu_info();
125 }
126
127 if use_parsed_schema {
128 println!("📦 Mode: ParsedSchema (parse once, reuse for all iterations)\n");
129 }
130
131 if use_cache {
132 println!("♻️ Mode: Cache (reuse JSONEval instance across iterations)\n");
133 }
134
135 if let Some(count) = concurrent_count {
136 println!("🔀 Concurrent evaluations: {} threads\n", count);
137 } else if iterations > 1 {
138 println!("🔄 Iterations per scenario: {}\n", iterations);
139 }
140
141 if enable_comparison {
142 println!("🔍 Comparison: enabled");
143 }
144 if show_timing {
145 println!("⏱️ Internal timing: enabled");
146 }
147 if enable_comparison || show_timing {
148 println!();
149 }
150
151 let samples_dir = Path::new("samples");
152 let mut scenarios = common::discover_scenarios(samples_dir);
153
154 // Filter scenarios if a filter is provided
155 if let Some(ref filter) = scenario_filter {
156 scenarios.retain(|s| s.name.contains(filter));
157 println!("📋 Filtering scenarios matching: '{}'\n", filter);
158 }
159
160 if scenarios.is_empty() {
161 if let Some(filter) = scenario_filter {
162 println!(
163 "ℹ️ No scenarios found matching '{}' in `{}`.",
164 filter,
165 samples_dir.display()
166 );
167 } else {
168 println!(
169 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
170 samples_dir.display()
171 );
172 }
173 return;
174 }
175
176 println!("📊 Found {} scenario(s)\n", scenarios.len());
177
178 let mut total_parse_time = std::time::Duration::ZERO;
179 let mut total_eval_time = std::time::Duration::ZERO;
180 let mut successful_scenarios = 0;
181 let mut comparison_failures = 0;
182
183 for scenario in &scenarios {
184 println!("==============================");
185 println!("Scenario: {}", scenario.name);
186 println!(
187 "Schema: {} ({})",
188 scenario.schema_path.display(),
189 if scenario.is_msgpack {
190 "MessagePack"
191 } else {
192 "JSON"
193 }
194 );
195 println!("Data: {}\n", scenario.data_path.display());
196
197 // Clear timing data from previous scenarios
198 if show_timing {
199 json_eval_rs::enable_timing();
200 json_eval_rs::clear_timing_data();
201 }
202
203 let data_str = fs::read_to_string(&scenario.data_path)
204 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
205
206 println!("Running evaluation...\n");
207
208 let (parse_time, eval_time, evaluated_schema, eval, iteration_times) = if use_parsed_schema
209 {
210 // ParsedSchema mode: parse once, reuse for all iterations/threads
211 let start_time = Instant::now();
212
213 let parsed_schema = if scenario.is_msgpack {
214 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
215 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
216 });
217 println!(
218 " 📦 MessagePack schema size: {} bytes",
219 schema_msgpack.len()
220 );
221 Arc::new(
222 ParsedSchema::parse_msgpack(&schema_msgpack)
223 .unwrap_or_else(|e| panic!("failed to parse MessagePack schema: {}", e)),
224 )
225 } else {
226 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
227 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
228 });
229 Arc::new(
230 ParsedSchema::parse(&schema_str)
231 .unwrap_or_else(|e| panic!("failed to parse schema: {}", e)),
232 )
233 };
234
235 let parse_time = start_time.elapsed();
236 println!(" Schema parsing & compilation: {:?}", parse_time);
237
238 // Concurrent mode with ParsedSchema
239 if let Some(thread_count) = concurrent_count {
240 use std::thread;
241
242 let eval_start = Instant::now();
243 let mut handles = vec![];
244
245 for thread_id in 0..thread_count {
246 let parsed_clone = parsed_schema.clone();
247 let data_str_clone = data_str.clone();
248 let iter_count = iterations;
249 let thread_use_cache = use_cache;
250
251 let handle = thread::spawn(move || {
252 let mut thread_times = Vec::with_capacity(iter_count);
253 let mut last_schema = Value::Null;
254
255 let mut eval_instance = JSONEval::with_parsed_schema(
256 parsed_clone.clone(),
257 Some("{}"),
258 Some(&data_str_clone),
259 )
260 .unwrap();
261
262 for iter in 0..iter_count {
263 let iter_start = Instant::now();
264
265 if !thread_use_cache && iter > 0 {
266 eval_instance = JSONEval::with_parsed_schema(
267 parsed_clone.clone(),
268 Some("{}"),
269 Some(&data_str_clone),
270 )
271 .unwrap();
272 }
273
274 eval_instance
275 .evaluate(&data_str_clone, Some("{}"), None, None)
276 .unwrap();
277 last_schema = eval_instance.get_evaluated_schema(false);
278 thread_times.push(iter_start.elapsed());
279 }
280
281 (thread_times, last_schema, thread_id)
282 });
283 handles.push(handle);
284 }
285
286 let mut all_iteration_times = Vec::new();
287 let mut evaluated_schema = Value::Null;
288
289 for handle in handles {
290 let (thread_times, thread_schema, thread_id) = handle.join().unwrap();
291 println!(
292 " Thread {} completed {} iterations",
293 thread_id,
294 thread_times.len()
295 );
296 all_iteration_times.extend(thread_times);
297 evaluated_schema = thread_schema; // Use last thread's result
298 }
299
300 let eval_time = eval_start.elapsed();
301
302 // Create a temp eval for metadata export
303 let temp_eval = JSONEval::with_parsed_schema(
304 parsed_schema.clone(),
305 Some("{}"),
306 Some(&data_str),
307 )
308 .unwrap();
309
310 (
311 parse_time,
312 eval_time,
313 evaluated_schema,
314 temp_eval,
315 all_iteration_times,
316 )
317 } else {
318 // Sequential iterations with ParsedSchema
319 let eval_start = Instant::now();
320 let mut evaluated_schema = Value::Null;
321 let mut iteration_times = Vec::with_capacity(iterations);
322 let mut eval_instance = JSONEval::with_parsed_schema(
323 parsed_schema.clone(),
324 Some("{}"),
325 Some(&data_str),
326 )
327 .unwrap();
328
329 for iter in 0..iterations {
330 let iter_start = Instant::now();
331
332 if !use_cache && iter > 0 {
333 eval_instance = JSONEval::with_parsed_schema(
334 parsed_schema.clone(),
335 Some("{}"),
336 Some(&data_str),
337 )
338 .unwrap();
339 }
340
341 eval_instance
342 .evaluate(&data_str, Some("{}"), None, None)
343 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
344 evaluated_schema = eval_instance.get_evaluated_schema(false);
345 iteration_times.push(iter_start.elapsed());
346
347 if iterations > 1 && (iter + 1) % 10 == 0 {
348 print!(".");
349 if (iter + 1) % 50 == 0 {
350 println!(" {}/{}", iter + 1, iterations);
351 }
352 }
353 }
354
355 if iterations > 1 && iterations % 50 != 0 {
356 println!(" {}/{}", iterations, iterations);
357 }
358
359 let eval_time = eval_start.elapsed();
360 (
361 parse_time,
362 eval_time,
363 evaluated_schema,
364 eval_instance,
365 iteration_times,
366 )
367 }
368 } else {
369 // Traditional mode: parse and create JSONEval each time
370 let schema_msgpack = if scenario.is_msgpack {
371 let bytes = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
372 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
373 });
374 println!(" 📦 MessagePack schema size: {} bytes", bytes.len());
375 Some(bytes)
376 } else {
377 None
378 };
379
380 let schema_str = if !scenario.is_msgpack {
381 Some(
382 fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
383 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
384 }),
385 )
386 } else {
387 None
388 };
389
390 let start_time = Instant::now();
391 let mut eval = if scenario.is_msgpack {
392 JSONEval::new_from_msgpack(schema_msgpack.as_ref().unwrap(), None, Some(&data_str))
393 .unwrap_or_else(|e| panic!("failed to create JSONEval from MessagePack: {}", e))
394 } else {
395 JSONEval::new(schema_str.as_ref().unwrap(), None, Some(&data_str))
396 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
397 };
398 let parse_time = start_time.elapsed();
399 println!(" Schema parsing & compilation: {:?}", parse_time);
400
401 let eval_start = Instant::now();
402 let mut evaluated_schema = Value::Null;
403 let mut iteration_times = Vec::with_capacity(iterations);
404
405 for iter in 0..iterations {
406 let iter_start = Instant::now();
407
408 if !use_cache && iter > 0 {
409 eval = if scenario.is_msgpack {
410 JSONEval::new_from_msgpack(
411 schema_msgpack.as_ref().unwrap(),
412 None,
413 Some(&data_str),
414 )
415 .unwrap_or_else(|e| {
416 panic!("failed to create JSONEval from MessagePack: {}", e)
417 })
418 } else {
419 JSONEval::new(schema_str.as_ref().unwrap(), None, Some(&data_str))
420 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
421 };
422 }
423
424 eval.evaluate(&data_str, Some("{}"), None, None)
425 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
426 evaluated_schema = eval.get_evaluated_schema(false);
427 iteration_times.push(iter_start.elapsed());
428
429 if iterations > 1 && (iter + 1) % 10 == 0 {
430 print!(".");
431 if (iter + 1) % 50 == 0 {
432 println!(" {}/{}", iter + 1, iterations);
433 }
434 }
435 }
436
437 if iterations > 1 && iterations % 50 != 0 {
438 println!(" {}/{}", iterations, iterations);
439 }
440
441 let eval_time = eval_start.elapsed();
442 (
443 parse_time,
444 eval_time,
445 evaluated_schema,
446 eval,
447 iteration_times,
448 )
449 };
450
451 // Calculate statistics
452 let total_iterations = iteration_times.len();
453 if total_iterations == 1 {
454 println!(" Evaluation: {:?}", eval_time);
455 } else {
456 let avg_time = eval_time / total_iterations as u32;
457 let min_time = iteration_times.iter().min().unwrap();
458 let max_time = iteration_times.iter().max().unwrap();
459
460 println!(" Total evaluation time: {:?}", eval_time);
461 println!(" Total iterations: {}", total_iterations);
462 println!(" Average per iteration: {:?}", avg_time);
463 println!(" Min: {:?} | Max: {:?}", min_time, max_time);
464 }
465
466 let total_time = parse_time + eval_time;
467 println!("⏱️ Execution time: {:?}\n", total_time);
468
469 // Print detailed timing breakdown if --timing flag is set
470 if show_timing {
471 json_eval_rs::print_timing_summary();
472 }
473
474 // Track statistics
475 total_parse_time += parse_time;
476 total_eval_time += eval_time;
477 successful_scenarios += 1;
478
479 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
480 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
481
482 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
483 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
484
485 let mut metadata_obj = Map::new();
486 metadata_obj.insert(
487 "dependencies".to_string(),
488 serde_json::to_value(&*eval.dependencies).unwrap(),
489 );
490 metadata_obj.insert(
491 "sorted_evaluations".to_string(),
492 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
493 );
494
495 fs::write(
496 &parsed_path,
497 common::pretty_json(&Value::Object(metadata_obj)),
498 )
499 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
500
501 println!("✅ Results saved:");
502 println!(" - {}", evaluated_path.display());
503 println!(" - {}\n", parsed_path.display());
504
505 // Optional comparison
506 if enable_comparison {
507 if let Some(comp_path) = &scenario.comparison_path {
508 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
509 comparison_failures += 1;
510 }
511 println!();
512 }
513 }
514 }
515
516 // Print summary statistics
517 if successful_scenarios > 0 {
518 println!("\n{}", "=".repeat(50));
519 println!("📊 Summary Statistics");
520 println!("{}", "=".repeat(50));
521 println!("Total scenarios run: {}", successful_scenarios);
522 println!("Total parsing time: {:?}", total_parse_time);
523 println!("Total evaluation time: {:?}", total_eval_time);
524 println!("Total time: {:?}", total_parse_time + total_eval_time);
525
526 if successful_scenarios > 1 {
527 println!("\nAverage per scenario:");
528 println!(
529 " Parsing: {:?}",
530 total_parse_time / successful_scenarios as u32
531 );
532 println!(
533 " Evaluation: {:?}",
534 total_eval_time / successful_scenarios as u32
535 );
536 }
537
538 if enable_comparison {
539 println!("\nComparison failures: {}", comparison_failures);
540 }
541
542 println!("\n✅ All scenarios completed successfully!\n");
543 }
544}Sourcepub fn with_parsed_schema(
parsed: Arc<ParsedSchema>,
context: Option<&str>,
data: Option<&str>,
) -> Result<Self, String>
pub fn with_parsed_schema( parsed: Arc<ParsedSchema>, context: Option<&str>, data: Option<&str>, ) -> Result<Self, String>
Create a new JSONEval instance from a pre-parsed ParsedSchema
This enables schema caching: parse once, reuse across multiple evaluations with different data/context.
§Arguments
parsed- Arc-wrapped pre-parsed schema (can be cloned and cached)context- Optional JSON context stringdata- Optional JSON data string
§Returns
A Result containing the JSONEval instance or an error
§Example
use std::sync::Arc;
// Parse schema once and wrap in Arc for caching
let parsed = Arc::new(ParsedSchema::parse(schema_str)?);
cache.insert(schema_key, parsed.clone());
// Reuse across multiple evaluations (Arc::clone is cheap)
let eval1 = JSONEval::with_parsed_schema(parsed.clone(), Some(context1), Some(data1))?;
let eval2 = JSONEval::with_parsed_schema(parsed.clone(), Some(context2), Some(data2))?;Examples found in repository?
37fn demo_local_cache() -> Result<(), Box<dyn std::error::Error>> {
38 println!("📦 Example 1: Local Cache Instance");
39 println!("Creating a dedicated cache for this application...\n");
40
41 let cache = ParsedSchemaCache::new();
42
43 // Simple schema
44 let schema_json = r#"{
45 "$params": {
46 "rate": { "type": "number" }
47 },
48 "result": {
49 "type": "number",
50 "title": "Calculated Result",
51 "$evaluation": {
52 "logic": { "*": [{"var": "$rate"}, 100] }
53 }
54 }
55 }"#;
56
57 // Parse and cache with a custom key
58 println!("📝 Parsing schema and caching with key 'calculation-v1'...");
59 let parsed = ParsedSchema::parse(schema_json)?;
60 cache.insert("calculation-v1".to_string(), Arc::new(parsed));
61
62 println!("✅ Schema cached successfully");
63 println!(" Cache size: {} entries", cache.len());
64 println!(" Keys: {:?}\n", cache.keys());
65
66 // Retrieve and use cached schema
67 println!("🔍 Retrieving cached schema...");
68 if let Some(cached_schema) = cache.get("calculation-v1") {
69 println!("✅ Retrieved from cache");
70
71 // Create JSONEval from cached ParsedSchema
72 let mut eval = JSONEval::with_parsed_schema(cached_schema, Some(r#"{"rate": 1.5}"#), None)?;
73 eval.evaluate("{}", None, None, None)?;
74
75 let evaluated = eval.get_evaluated_schema(false);
76 let result = evaluated
77 .pointer("/result")
78 .and_then(|v| v.as_f64())
79 .unwrap_or(0.0);
80 println!(" Evaluation result: {}\n", result);
81 }
82
83 // Check cache stats
84 let stats = cache.stats();
85 println!("📊 Cache Statistics: {}", stats);
86
87 // Remove entry
88 println!("\n🗑️ Removing 'calculation-v1' from cache...");
89 cache.remove("calculation-v1");
90 println!(" Cache size after removal: {}", cache.len());
91
92 Ok(())
93}
94
95fn demo_global_cache() -> Result<(), Box<dyn std::error::Error>> {
96 println!("🌍 Example 2: Global Cache Instance");
97 println!("Using the built-in PARSED_SCHEMA_CACHE...\n");
98
99 let schema_json = r#"{
100 "$params": {
101 "x": { "type": "number" },
102 "y": { "type": "number" }
103 },
104 "sum": {
105 "type": "number",
106 "$evaluation": { "+": [{"var": "$x"}, {"var": "$y"}] }
107 }
108 }"#;
109
110 // Use global cache
111 println!("📝 Caching schema globally with key 'math-operations'...");
112 let parsed = ParsedSchema::parse(schema_json)?;
113 PARSED_SCHEMA_CACHE.insert("math-operations".to_string(), Arc::new(parsed));
114
115 println!("✅ Schema cached globally");
116 println!(" Global cache size: {}\n", PARSED_SCHEMA_CACHE.len());
117
118 // Access from anywhere in the application
119 simulate_another_function()?;
120
121 // Clean up
122 println!("\n🧹 Clearing global cache...");
123 PARSED_SCHEMA_CACHE.clear();
124 println!(" Global cache size: {}", PARSED_SCHEMA_CACHE.len());
125
126 Ok(())
127}
128
129fn simulate_another_function() -> Result<(), Box<dyn std::error::Error>> {
130 println!("🔄 In another function, accessing global cache...");
131
132 if let Some(cached) = PARSED_SCHEMA_CACHE.get("math-operations") {
133 println!("✅ Retrieved schema from global cache");
134
135 let mut eval = JSONEval::with_parsed_schema(cached, Some(r#"{"x": 10, "y": 20}"#), None)?;
136 eval.evaluate("{}", None, None, None)?;
137
138 let evaluated = eval.get_evaluated_schema(false);
139 let sum = evaluated
140 .pointer("/sum")
141 .and_then(|v| v.as_f64())
142 .unwrap_or(0.0);
143 println!(" Result: {}", sum);
144 }
145
146 Ok(())
147}
148
149fn demo_performance_comparison() -> Result<(), Box<dyn std::error::Error>> {
150 println!("⚡ Example 3: Performance Comparison");
151 println!("Comparing cached vs non-cached schema usage...\n");
152
153 let schema_json = r#"{
154 "$params": {
155 "value": { "type": "number" }
156 },
157 "doubled": {
158 "type": "number",
159 "$evaluation": { "*": [{"var": "$value"}, 2] }
160 },
161 "tripled": {
162 "type": "number",
163 "$evaluation": { "*": [{"var": "$value"}, 3] }
164 }
165 }"#;
166
167 let iterations = 100;
168
169 // WITHOUT CACHE: Parse schema every time
170 println!("🐌 Without cache (parse + evaluate each time):");
171 let start = Instant::now();
172 for i in 0..iterations {
173 let context = format!(r#"{{"value": {}}}"#, i);
174 let mut eval = JSONEval::new(schema_json, Some(&context), None)?;
175 eval.evaluate("{}", None, None, None)?;
176 }
177 let without_cache = start.elapsed();
178 println!(" Time: {:?}", without_cache);
179 println!(" Avg per iteration: {:?}\n", without_cache / iterations);
180
181 // WITH CACHE: Parse once, evaluate many times
182 println!("🚀 With cache (parse once, reuse for all evaluations):");
183 let cache = ParsedSchemaCache::new();
184
185 // Parse once
186 let parse_start = Instant::now();
187 let parsed = ParsedSchema::parse(schema_json)?;
188 cache.insert("perf-test".to_string(), Arc::new(parsed));
189 let parse_time = parse_start.elapsed();
190
191 // Evaluate many times
192 let eval_start = Instant::now();
193 for i in 0..iterations {
194 if let Some(cached) = cache.get("perf-test") {
195 let context = format!(r#"{{"value": {}}}"#, i);
196 let mut eval = JSONEval::with_parsed_schema(cached.clone(), Some(&context), None)?;
197 eval.evaluate("{}", None, None, None)?;
198 }
199 }
200 let eval_time = eval_start.elapsed();
201 let with_cache = parse_time + eval_time;
202
203 println!(" Parse time: {:?}", parse_time);
204 println!(" Eval time: {:?}", eval_time);
205 println!(" Total time: {:?}", with_cache);
206 println!(" Avg per iteration: {:?}\n", eval_time / iterations);
207
208 let speedup = without_cache.as_secs_f64() / with_cache.as_secs_f64();
209 println!("📈 Speedup: {:.2}x faster", speedup);
210
211 Ok(())
212}More examples
42fn main() {
43 let args: Vec<String> = std::env::args().collect();
44 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("basic_parsed");
45
46 let mut scenario_filter: Option<String> = None;
47 let mut enable_comparison = false;
48 let mut show_timing = false;
49 let mut i = 1;
50
51 // Parse arguments
52 while i < args.len() {
53 let arg = &args[i];
54
55 if arg == "-h" || arg == "--help" {
56 print_help(program_name);
57 return;
58 } else if arg == "--compare" {
59 enable_comparison = true;
60 } else if arg == "--timing" {
61 show_timing = true;
62 } else if !arg.starts_with('-') {
63 scenario_filter = Some(arg.clone());
64 } else {
65 eprintln!("Error: unknown option '{}'", arg);
66 print_help(program_name);
67 return;
68 }
69
70 i += 1;
71 }
72
73 println!("\n🚀 JSON Evaluation - Basic Example (Parsed / JSON & MsgPack)\n");
74 println!("📦 Using Arc<ParsedSchema> for efficient caching\n");
75
76 if enable_comparison {
77 println!("🔍 Comparison: enabled");
78 }
79 if show_timing {
80 println!("⏱️ Internal timing: enabled");
81 }
82 if enable_comparison || show_timing {
83 println!();
84 }
85
86 let samples_dir = Path::new("samples");
87 let mut scenarios = common::discover_scenarios(samples_dir);
88
89 // Filter scenarios if a filter is provided
90 if let Some(ref filter) = scenario_filter {
91 scenarios.retain(|s| s.name.contains(filter));
92 println!("📋 Filtering scenarios matching: '{}'\n", filter);
93 }
94
95 if scenarios.is_empty() {
96 if let Some(filter) = scenario_filter {
97 println!(
98 "ℹ️ No scenarios found matching '{}' in `{}`.",
99 filter,
100 samples_dir.display()
101 );
102 } else {
103 println!(
104 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
105 samples_dir.display()
106 );
107 }
108 return;
109 }
110
111 println!("📊 Found {} scenario(s)\n", scenarios.len());
112
113 let mut total_parse_time = std::time::Duration::ZERO;
114 let mut total_eval_time = std::time::Duration::ZERO;
115 let mut successful_scenarios = 0;
116 let mut comparison_failures = 0;
117
118 for scenario in &scenarios {
119 println!("==============================");
120 println!("Scenario: {}", scenario.name);
121 println!(
122 "Schema: {} ({})",
123 scenario.schema_path.display(),
124 if scenario.is_msgpack {
125 "MessagePack"
126 } else {
127 "JSON"
128 }
129 );
130 println!("Data: {}\n", scenario.data_path.display());
131
132 // Clear timing data from previous scenarios
133 if show_timing {
134 json_eval_rs::enable_timing();
135 json_eval_rs::clear_timing_data();
136 }
137
138 let data_str = fs::read_to_string(&scenario.data_path)
139 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
140
141 // Step 1: Parse schema once
142 let parse_start = Instant::now();
143 let parsed_schema = if scenario.is_msgpack {
144 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
145 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
146 });
147 println!(
148 " 📦 MessagePack schema size: {} bytes",
149 schema_msgpack.len()
150 );
151 Arc::new(
152 ParsedSchema::parse_msgpack(&schema_msgpack)
153 .unwrap_or_else(|e| panic!("failed to parse MessagePack schema: {}", e)),
154 )
155 } else {
156 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
157 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
158 });
159 Arc::new(
160 ParsedSchema::parse(&schema_str)
161 .unwrap_or_else(|e| panic!("failed to parse schema: {}", e)),
162 )
163 };
164 let parse_time = parse_start.elapsed();
165 println!(" 📝 Schema parsing: {:?}", parse_time);
166
167 // Step 2: Create JSONEval from ParsedSchema (reuses compiled logic)
168 let eval_start = Instant::now();
169 let mut eval = JSONEval::with_parsed_schema(
170 parsed_schema.clone(), // Arc::clone is cheap!
171 Some("{}"),
172 Some(&data_str),
173 )
174 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e));
175
176 eval.evaluate(&data_str, Some("{}"), None, None)
177 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
178
179 let evaluated_schema = eval.get_evaluated_schema(false);
180 let eval_time = eval_start.elapsed();
181
182 println!(" ⚡ Eval: {:?}", eval_time);
183 println!(" ⏱️ Total: {:?}\n", parse_time + eval_time);
184
185 // Print detailed timing breakdown if --timing flag is set
186 if show_timing {
187 json_eval_rs::print_timing_summary();
188 }
189
190 total_parse_time += parse_time;
191 total_eval_time += eval_time;
192 successful_scenarios += 1;
193
194 // Save results
195 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
196 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
197
198 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
199 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
200
201 let mut metadata_obj = Map::new();
202 metadata_obj.insert(
203 "dependencies".to_string(),
204 serde_json::to_value(&*eval.dependencies).unwrap(),
205 );
206 metadata_obj.insert(
207 "evaluations".to_string(),
208 serde_json::to_value(&*eval.evaluations).unwrap(),
209 );
210 metadata_obj.insert(
211 "sorted_evaluations".to_string(),
212 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
213 );
214
215 fs::write(
216 &parsed_path,
217 common::pretty_json(&Value::Object(metadata_obj)),
218 )
219 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
220
221 println!("✅ Results saved:");
222 println!(" - {}", evaluated_path.display());
223 println!(" - {}\n", parsed_path.display());
224
225 // Optional comparison
226 if enable_comparison {
227 if let Some(comp_path) = &scenario.comparison_path {
228 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
229 comparison_failures += 1;
230 }
231 println!();
232 }
233 }
234 }
235
236 // Print summary
237 println!("{}", "=".repeat(50));
238 println!("📊 Summary");
239 println!("{}", "=".repeat(50));
240 println!("Total scenarios run: {}", successful_scenarios);
241 println!("Total parsing time: {:?}", total_parse_time);
242 println!("Total evaluation time: {:?}", total_eval_time);
243 println!("Total time: {:?}", total_parse_time + total_eval_time);
244
245 if successful_scenarios > 1 {
246 println!("\nAverage per scenario:");
247 println!(
248 " Parsing: {:?}",
249 total_parse_time / successful_scenarios as u32
250 );
251 println!(
252 " Evaluation: {:?}",
253 total_eval_time / successful_scenarios as u32
254 );
255 }
256
257 if enable_comparison {
258 println!("\nComparison failures: {}", comparison_failures);
259 }
260
261 println!("\n✅ All scenarios completed!\n");
262}44fn main() {
45 let args: Vec<String> = std::env::args().collect();
46 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("benchmark");
47
48 let mut iterations = 1usize;
49 let mut scenario_filter: Option<String> = None;
50 let mut show_cpu_info = false;
51 let mut use_parsed_schema = false;
52 let mut use_cache = false;
53 let mut concurrent_count: Option<usize> = None;
54 let mut enable_comparison = false;
55 let mut show_timing = false;
56 let mut i = 1;
57
58 // Parse arguments
59 while i < args.len() {
60 let arg = &args[i];
61
62 if arg == "-h" || arg == "--help" {
63 print_help(program_name);
64 return;
65 } else if arg == "--cpu-info" {
66 show_cpu_info = true;
67 } else if arg == "--parsed" {
68 use_parsed_schema = true;
69 } else if arg == "--cache" {
70 use_cache = true;
71 } else if arg == "--compare" {
72 enable_comparison = true;
73 } else if arg == "--timing" {
74 show_timing = true;
75 } else if arg == "--concurrent" {
76 if i + 1 >= args.len() {
77 eprintln!("Error: {} requires a value", arg);
78 print_help(program_name);
79 return;
80 }
81 i += 1;
82 match args[i].parse::<usize>() {
83 Ok(n) if n > 0 => concurrent_count = Some(n),
84 _ => {
85 eprintln!(
86 "Error: concurrent count must be a positive integer, got '{}'",
87 args[i]
88 );
89 return;
90 }
91 }
92 } else if arg == "-i" || arg == "--iterations" {
93 if i + 1 >= args.len() {
94 eprintln!("Error: {} requires a value", arg);
95 print_help(program_name);
96 return;
97 }
98 i += 1;
99 match args[i].parse::<usize>() {
100 Ok(n) if n > 0 => iterations = n,
101 _ => {
102 eprintln!(
103 "Error: iterations must be a positive integer, got '{}'",
104 args[i]
105 );
106 return;
107 }
108 }
109 } else if !arg.starts_with('-') {
110 scenario_filter = Some(arg.clone());
111 } else {
112 eprintln!("Error: unknown option '{}'", arg);
113 print_help(program_name);
114 return;
115 }
116
117 i += 1;
118 }
119
120 println!("\n🚀 JSON Evaluation - Benchmark\n");
121
122 // Show CPU info if requested or if running benchmarks
123 if show_cpu_info || iterations > 1 || concurrent_count.is_some() {
124 common::print_cpu_info();
125 }
126
127 if use_parsed_schema {
128 println!("📦 Mode: ParsedSchema (parse once, reuse for all iterations)\n");
129 }
130
131 if use_cache {
132 println!("♻️ Mode: Cache (reuse JSONEval instance across iterations)\n");
133 }
134
135 if let Some(count) = concurrent_count {
136 println!("🔀 Concurrent evaluations: {} threads\n", count);
137 } else if iterations > 1 {
138 println!("🔄 Iterations per scenario: {}\n", iterations);
139 }
140
141 if enable_comparison {
142 println!("🔍 Comparison: enabled");
143 }
144 if show_timing {
145 println!("⏱️ Internal timing: enabled");
146 }
147 if enable_comparison || show_timing {
148 println!();
149 }
150
151 let samples_dir = Path::new("samples");
152 let mut scenarios = common::discover_scenarios(samples_dir);
153
154 // Filter scenarios if a filter is provided
155 if let Some(ref filter) = scenario_filter {
156 scenarios.retain(|s| s.name.contains(filter));
157 println!("📋 Filtering scenarios matching: '{}'\n", filter);
158 }
159
160 if scenarios.is_empty() {
161 if let Some(filter) = scenario_filter {
162 println!(
163 "ℹ️ No scenarios found matching '{}' in `{}`.",
164 filter,
165 samples_dir.display()
166 );
167 } else {
168 println!(
169 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
170 samples_dir.display()
171 );
172 }
173 return;
174 }
175
176 println!("📊 Found {} scenario(s)\n", scenarios.len());
177
178 let mut total_parse_time = std::time::Duration::ZERO;
179 let mut total_eval_time = std::time::Duration::ZERO;
180 let mut successful_scenarios = 0;
181 let mut comparison_failures = 0;
182
183 for scenario in &scenarios {
184 println!("==============================");
185 println!("Scenario: {}", scenario.name);
186 println!(
187 "Schema: {} ({})",
188 scenario.schema_path.display(),
189 if scenario.is_msgpack {
190 "MessagePack"
191 } else {
192 "JSON"
193 }
194 );
195 println!("Data: {}\n", scenario.data_path.display());
196
197 // Clear timing data from previous scenarios
198 if show_timing {
199 json_eval_rs::enable_timing();
200 json_eval_rs::clear_timing_data();
201 }
202
203 let data_str = fs::read_to_string(&scenario.data_path)
204 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
205
206 println!("Running evaluation...\n");
207
208 let (parse_time, eval_time, evaluated_schema, eval, iteration_times) = if use_parsed_schema
209 {
210 // ParsedSchema mode: parse once, reuse for all iterations/threads
211 let start_time = Instant::now();
212
213 let parsed_schema = if scenario.is_msgpack {
214 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
215 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
216 });
217 println!(
218 " 📦 MessagePack schema size: {} bytes",
219 schema_msgpack.len()
220 );
221 Arc::new(
222 ParsedSchema::parse_msgpack(&schema_msgpack)
223 .unwrap_or_else(|e| panic!("failed to parse MessagePack schema: {}", e)),
224 )
225 } else {
226 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
227 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
228 });
229 Arc::new(
230 ParsedSchema::parse(&schema_str)
231 .unwrap_or_else(|e| panic!("failed to parse schema: {}", e)),
232 )
233 };
234
235 let parse_time = start_time.elapsed();
236 println!(" Schema parsing & compilation: {:?}", parse_time);
237
238 // Concurrent mode with ParsedSchema
239 if let Some(thread_count) = concurrent_count {
240 use std::thread;
241
242 let eval_start = Instant::now();
243 let mut handles = vec![];
244
245 for thread_id in 0..thread_count {
246 let parsed_clone = parsed_schema.clone();
247 let data_str_clone = data_str.clone();
248 let iter_count = iterations;
249 let thread_use_cache = use_cache;
250
251 let handle = thread::spawn(move || {
252 let mut thread_times = Vec::with_capacity(iter_count);
253 let mut last_schema = Value::Null;
254
255 let mut eval_instance = JSONEval::with_parsed_schema(
256 parsed_clone.clone(),
257 Some("{}"),
258 Some(&data_str_clone),
259 )
260 .unwrap();
261
262 for iter in 0..iter_count {
263 let iter_start = Instant::now();
264
265 if !thread_use_cache && iter > 0 {
266 eval_instance = JSONEval::with_parsed_schema(
267 parsed_clone.clone(),
268 Some("{}"),
269 Some(&data_str_clone),
270 )
271 .unwrap();
272 }
273
274 eval_instance
275 .evaluate(&data_str_clone, Some("{}"), None, None)
276 .unwrap();
277 last_schema = eval_instance.get_evaluated_schema(false);
278 thread_times.push(iter_start.elapsed());
279 }
280
281 (thread_times, last_schema, thread_id)
282 });
283 handles.push(handle);
284 }
285
286 let mut all_iteration_times = Vec::new();
287 let mut evaluated_schema = Value::Null;
288
289 for handle in handles {
290 let (thread_times, thread_schema, thread_id) = handle.join().unwrap();
291 println!(
292 " Thread {} completed {} iterations",
293 thread_id,
294 thread_times.len()
295 );
296 all_iteration_times.extend(thread_times);
297 evaluated_schema = thread_schema; // Use last thread's result
298 }
299
300 let eval_time = eval_start.elapsed();
301
302 // Create a temp eval for metadata export
303 let temp_eval = JSONEval::with_parsed_schema(
304 parsed_schema.clone(),
305 Some("{}"),
306 Some(&data_str),
307 )
308 .unwrap();
309
310 (
311 parse_time,
312 eval_time,
313 evaluated_schema,
314 temp_eval,
315 all_iteration_times,
316 )
317 } else {
318 // Sequential iterations with ParsedSchema
319 let eval_start = Instant::now();
320 let mut evaluated_schema = Value::Null;
321 let mut iteration_times = Vec::with_capacity(iterations);
322 let mut eval_instance = JSONEval::with_parsed_schema(
323 parsed_schema.clone(),
324 Some("{}"),
325 Some(&data_str),
326 )
327 .unwrap();
328
329 for iter in 0..iterations {
330 let iter_start = Instant::now();
331
332 if !use_cache && iter > 0 {
333 eval_instance = JSONEval::with_parsed_schema(
334 parsed_schema.clone(),
335 Some("{}"),
336 Some(&data_str),
337 )
338 .unwrap();
339 }
340
341 eval_instance
342 .evaluate(&data_str, Some("{}"), None, None)
343 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
344 evaluated_schema = eval_instance.get_evaluated_schema(false);
345 iteration_times.push(iter_start.elapsed());
346
347 if iterations > 1 && (iter + 1) % 10 == 0 {
348 print!(".");
349 if (iter + 1) % 50 == 0 {
350 println!(" {}/{}", iter + 1, iterations);
351 }
352 }
353 }
354
355 if iterations > 1 && iterations % 50 != 0 {
356 println!(" {}/{}", iterations, iterations);
357 }
358
359 let eval_time = eval_start.elapsed();
360 (
361 parse_time,
362 eval_time,
363 evaluated_schema,
364 eval_instance,
365 iteration_times,
366 )
367 }
368 } else {
369 // Traditional mode: parse and create JSONEval each time
370 let schema_msgpack = if scenario.is_msgpack {
371 let bytes = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
372 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
373 });
374 println!(" 📦 MessagePack schema size: {} bytes", bytes.len());
375 Some(bytes)
376 } else {
377 None
378 };
379
380 let schema_str = if !scenario.is_msgpack {
381 Some(
382 fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
383 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
384 }),
385 )
386 } else {
387 None
388 };
389
390 let start_time = Instant::now();
391 let mut eval = if scenario.is_msgpack {
392 JSONEval::new_from_msgpack(schema_msgpack.as_ref().unwrap(), None, Some(&data_str))
393 .unwrap_or_else(|e| panic!("failed to create JSONEval from MessagePack: {}", e))
394 } else {
395 JSONEval::new(schema_str.as_ref().unwrap(), None, Some(&data_str))
396 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
397 };
398 let parse_time = start_time.elapsed();
399 println!(" Schema parsing & compilation: {:?}", parse_time);
400
401 let eval_start = Instant::now();
402 let mut evaluated_schema = Value::Null;
403 let mut iteration_times = Vec::with_capacity(iterations);
404
405 for iter in 0..iterations {
406 let iter_start = Instant::now();
407
408 if !use_cache && iter > 0 {
409 eval = if scenario.is_msgpack {
410 JSONEval::new_from_msgpack(
411 schema_msgpack.as_ref().unwrap(),
412 None,
413 Some(&data_str),
414 )
415 .unwrap_or_else(|e| {
416 panic!("failed to create JSONEval from MessagePack: {}", e)
417 })
418 } else {
419 JSONEval::new(schema_str.as_ref().unwrap(), None, Some(&data_str))
420 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
421 };
422 }
423
424 eval.evaluate(&data_str, Some("{}"), None, None)
425 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
426 evaluated_schema = eval.get_evaluated_schema(false);
427 iteration_times.push(iter_start.elapsed());
428
429 if iterations > 1 && (iter + 1) % 10 == 0 {
430 print!(".");
431 if (iter + 1) % 50 == 0 {
432 println!(" {}/{}", iter + 1, iterations);
433 }
434 }
435 }
436
437 if iterations > 1 && iterations % 50 != 0 {
438 println!(" {}/{}", iterations, iterations);
439 }
440
441 let eval_time = eval_start.elapsed();
442 (
443 parse_time,
444 eval_time,
445 evaluated_schema,
446 eval,
447 iteration_times,
448 )
449 };
450
451 // Calculate statistics
452 let total_iterations = iteration_times.len();
453 if total_iterations == 1 {
454 println!(" Evaluation: {:?}", eval_time);
455 } else {
456 let avg_time = eval_time / total_iterations as u32;
457 let min_time = iteration_times.iter().min().unwrap();
458 let max_time = iteration_times.iter().max().unwrap();
459
460 println!(" Total evaluation time: {:?}", eval_time);
461 println!(" Total iterations: {}", total_iterations);
462 println!(" Average per iteration: {:?}", avg_time);
463 println!(" Min: {:?} | Max: {:?}", min_time, max_time);
464 }
465
466 let total_time = parse_time + eval_time;
467 println!("⏱️ Execution time: {:?}\n", total_time);
468
469 // Print detailed timing breakdown if --timing flag is set
470 if show_timing {
471 json_eval_rs::print_timing_summary();
472 }
473
474 // Track statistics
475 total_parse_time += parse_time;
476 total_eval_time += eval_time;
477 successful_scenarios += 1;
478
479 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
480 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
481
482 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
483 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
484
485 let mut metadata_obj = Map::new();
486 metadata_obj.insert(
487 "dependencies".to_string(),
488 serde_json::to_value(&*eval.dependencies).unwrap(),
489 );
490 metadata_obj.insert(
491 "sorted_evaluations".to_string(),
492 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
493 );
494
495 fs::write(
496 &parsed_path,
497 common::pretty_json(&Value::Object(metadata_obj)),
498 )
499 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
500
501 println!("✅ Results saved:");
502 println!(" - {}", evaluated_path.display());
503 println!(" - {}\n", parsed_path.display());
504
505 // Optional comparison
506 if enable_comparison {
507 if let Some(comp_path) = &scenario.comparison_path {
508 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
509 comparison_failures += 1;
510 }
511 println!();
512 }
513 }
514 }
515
516 // Print summary statistics
517 if successful_scenarios > 0 {
518 println!("\n{}", "=".repeat(50));
519 println!("📊 Summary Statistics");
520 println!("{}", "=".repeat(50));
521 println!("Total scenarios run: {}", successful_scenarios);
522 println!("Total parsing time: {:?}", total_parse_time);
523 println!("Total evaluation time: {:?}", total_eval_time);
524 println!("Total time: {:?}", total_parse_time + total_eval_time);
525
526 if successful_scenarios > 1 {
527 println!("\nAverage per scenario:");
528 println!(
529 " Parsing: {:?}",
530 total_parse_time / successful_scenarios as u32
531 );
532 println!(
533 " Evaluation: {:?}",
534 total_eval_time / successful_scenarios as u32
535 );
536 }
537
538 if enable_comparison {
539 println!("\nComparison failures: {}", comparison_failures);
540 }
541
542 println!("\n✅ All scenarios completed successfully!\n");
543 }
544}pub fn reload_schema( &mut self, schema: &str, context: Option<&str>, data: Option<&str>, ) -> Result<(), String>
Sourcepub fn set_timezone_offset(&mut self, offset_minutes: Option<i32>)
pub fn set_timezone_offset(&mut self, offset_minutes: Option<i32>)
Set the timezone offset for datetime operations (TODAY, NOW)
This method updates the RLogic engine configuration with a new timezone offset. The offset will be applied to all subsequent datetime evaluations.
§Arguments
offset_minutes- Timezone offset in minutes from UTC (e.g., 420 for UTC+7, -300 for UTC-5) PassNoneto reset to UTC (no offset)
§Example
let mut eval = JSONEval::new(schema, None, None)?;
// Set to UTC+7 (Jakarta, Bangkok)
eval.set_timezone_offset(Some(420));
// Reset to UTC
eval.set_timezone_offset(None);Sourcepub fn reload_schema_msgpack(
&mut self,
schema_msgpack: &[u8],
context: Option<&str>,
data: Option<&str>,
) -> Result<(), String>
pub fn reload_schema_msgpack( &mut self, schema_msgpack: &[u8], context: Option<&str>, data: Option<&str>, ) -> Result<(), String>
Sourcepub fn reload_schema_parsed(
&mut self,
parsed: Arc<ParsedSchema>,
context: Option<&str>,
data: Option<&str>,
) -> Result<(), String>
pub fn reload_schema_parsed( &mut self, parsed: Arc<ParsedSchema>, context: Option<&str>, data: Option<&str>, ) -> Result<(), String>
Reload schema from a cached ParsedSchema
This is the most efficient way to reload as it reuses pre-parsed schema compilation.
§Arguments
parsed- Arc reference to a cached ParsedSchemacontext- Optional context data JSON stringdata- Optional initial data JSON string
§Returns
A Result indicating success or an error message
Sourcepub fn reload_schema_from_cache(
&mut self,
cache_key: &str,
context: Option<&str>,
data: Option<&str>,
) -> Result<(), String>
pub fn reload_schema_from_cache( &mut self, cache_key: &str, context: Option<&str>, data: Option<&str>, ) -> Result<(), String>
Reload schema from ParsedSchemaCache using a cache key
This is the recommended way for cross-platform cached schema reloading.
§Arguments
cache_key- Key to lookup in the global ParsedSchemaCachecontext- Optional context data JSON stringdata- Optional initial data JSON string
§Returns
A Result indicating success or an error message
Source§impl JSONEval
impl JSONEval
Sourcepub fn evaluate_dependents(
&mut self,
changed_paths: &[String],
data: Option<&str>,
context: Option<&str>,
re_evaluate: bool,
token: Option<&CancellationToken>,
canceled_paths: Option<&mut Vec<String>>,
include_subforms: bool,
) -> Result<Value, String>
pub fn evaluate_dependents( &mut self, changed_paths: &[String], data: Option<&str>, context: Option<&str>, re_evaluate: bool, token: Option<&CancellationToken>, canceled_paths: Option<&mut Vec<String>>, include_subforms: bool, ) -> Result<Value, String>
Evaluate fields that depend on a changed path. Processes all dependent fields transitively, then optionally performs a full re-evaluation pass (for read-only / hide effects) and cascades into subforms.
Source§impl JSONEval
impl JSONEval
Sourcepub fn evaluate(
&mut self,
data: &str,
context: Option<&str>,
paths: Option<&[String]>,
token: Option<&CancellationToken>,
) -> Result<(), String>
pub fn evaluate( &mut self, data: &str, context: Option<&str>, paths: Option<&[String]>, token: Option<&CancellationToken>, ) -> Result<(), String>
Evaluate the schema with the given data and context.
§Arguments
data- The data to evaluate.context- The context to evaluate.
§Returns
A Result indicating success or an error message.
Examples found in repository?
37fn demo_local_cache() -> Result<(), Box<dyn std::error::Error>> {
38 println!("📦 Example 1: Local Cache Instance");
39 println!("Creating a dedicated cache for this application...\n");
40
41 let cache = ParsedSchemaCache::new();
42
43 // Simple schema
44 let schema_json = r#"{
45 "$params": {
46 "rate": { "type": "number" }
47 },
48 "result": {
49 "type": "number",
50 "title": "Calculated Result",
51 "$evaluation": {
52 "logic": { "*": [{"var": "$rate"}, 100] }
53 }
54 }
55 }"#;
56
57 // Parse and cache with a custom key
58 println!("📝 Parsing schema and caching with key 'calculation-v1'...");
59 let parsed = ParsedSchema::parse(schema_json)?;
60 cache.insert("calculation-v1".to_string(), Arc::new(parsed));
61
62 println!("✅ Schema cached successfully");
63 println!(" Cache size: {} entries", cache.len());
64 println!(" Keys: {:?}\n", cache.keys());
65
66 // Retrieve and use cached schema
67 println!("🔍 Retrieving cached schema...");
68 if let Some(cached_schema) = cache.get("calculation-v1") {
69 println!("✅ Retrieved from cache");
70
71 // Create JSONEval from cached ParsedSchema
72 let mut eval = JSONEval::with_parsed_schema(cached_schema, Some(r#"{"rate": 1.5}"#), None)?;
73 eval.evaluate("{}", None, None, None)?;
74
75 let evaluated = eval.get_evaluated_schema(false);
76 let result = evaluated
77 .pointer("/result")
78 .and_then(|v| v.as_f64())
79 .unwrap_or(0.0);
80 println!(" Evaluation result: {}\n", result);
81 }
82
83 // Check cache stats
84 let stats = cache.stats();
85 println!("📊 Cache Statistics: {}", stats);
86
87 // Remove entry
88 println!("\n🗑️ Removing 'calculation-v1' from cache...");
89 cache.remove("calculation-v1");
90 println!(" Cache size after removal: {}", cache.len());
91
92 Ok(())
93}
94
95fn demo_global_cache() -> Result<(), Box<dyn std::error::Error>> {
96 println!("🌍 Example 2: Global Cache Instance");
97 println!("Using the built-in PARSED_SCHEMA_CACHE...\n");
98
99 let schema_json = r#"{
100 "$params": {
101 "x": { "type": "number" },
102 "y": { "type": "number" }
103 },
104 "sum": {
105 "type": "number",
106 "$evaluation": { "+": [{"var": "$x"}, {"var": "$y"}] }
107 }
108 }"#;
109
110 // Use global cache
111 println!("📝 Caching schema globally with key 'math-operations'...");
112 let parsed = ParsedSchema::parse(schema_json)?;
113 PARSED_SCHEMA_CACHE.insert("math-operations".to_string(), Arc::new(parsed));
114
115 println!("✅ Schema cached globally");
116 println!(" Global cache size: {}\n", PARSED_SCHEMA_CACHE.len());
117
118 // Access from anywhere in the application
119 simulate_another_function()?;
120
121 // Clean up
122 println!("\n🧹 Clearing global cache...");
123 PARSED_SCHEMA_CACHE.clear();
124 println!(" Global cache size: {}", PARSED_SCHEMA_CACHE.len());
125
126 Ok(())
127}
128
129fn simulate_another_function() -> Result<(), Box<dyn std::error::Error>> {
130 println!("🔄 In another function, accessing global cache...");
131
132 if let Some(cached) = PARSED_SCHEMA_CACHE.get("math-operations") {
133 println!("✅ Retrieved schema from global cache");
134
135 let mut eval = JSONEval::with_parsed_schema(cached, Some(r#"{"x": 10, "y": 20}"#), None)?;
136 eval.evaluate("{}", None, None, None)?;
137
138 let evaluated = eval.get_evaluated_schema(false);
139 let sum = evaluated
140 .pointer("/sum")
141 .and_then(|v| v.as_f64())
142 .unwrap_or(0.0);
143 println!(" Result: {}", sum);
144 }
145
146 Ok(())
147}
148
149fn demo_performance_comparison() -> Result<(), Box<dyn std::error::Error>> {
150 println!("⚡ Example 3: Performance Comparison");
151 println!("Comparing cached vs non-cached schema usage...\n");
152
153 let schema_json = r#"{
154 "$params": {
155 "value": { "type": "number" }
156 },
157 "doubled": {
158 "type": "number",
159 "$evaluation": { "*": [{"var": "$value"}, 2] }
160 },
161 "tripled": {
162 "type": "number",
163 "$evaluation": { "*": [{"var": "$value"}, 3] }
164 }
165 }"#;
166
167 let iterations = 100;
168
169 // WITHOUT CACHE: Parse schema every time
170 println!("🐌 Without cache (parse + evaluate each time):");
171 let start = Instant::now();
172 for i in 0..iterations {
173 let context = format!(r#"{{"value": {}}}"#, i);
174 let mut eval = JSONEval::new(schema_json, Some(&context), None)?;
175 eval.evaluate("{}", None, None, None)?;
176 }
177 let without_cache = start.elapsed();
178 println!(" Time: {:?}", without_cache);
179 println!(" Avg per iteration: {:?}\n", without_cache / iterations);
180
181 // WITH CACHE: Parse once, evaluate many times
182 println!("🚀 With cache (parse once, reuse for all evaluations):");
183 let cache = ParsedSchemaCache::new();
184
185 // Parse once
186 let parse_start = Instant::now();
187 let parsed = ParsedSchema::parse(schema_json)?;
188 cache.insert("perf-test".to_string(), Arc::new(parsed));
189 let parse_time = parse_start.elapsed();
190
191 // Evaluate many times
192 let eval_start = Instant::now();
193 for i in 0..iterations {
194 if let Some(cached) = cache.get("perf-test") {
195 let context = format!(r#"{{"value": {}}}"#, i);
196 let mut eval = JSONEval::with_parsed_schema(cached.clone(), Some(&context), None)?;
197 eval.evaluate("{}", None, None, None)?;
198 }
199 }
200 let eval_time = eval_start.elapsed();
201 let with_cache = parse_time + eval_time;
202
203 println!(" Parse time: {:?}", parse_time);
204 println!(" Eval time: {:?}", eval_time);
205 println!(" Total time: {:?}", with_cache);
206 println!(" Avg per iteration: {:?}\n", eval_time / iterations);
207
208 let speedup = without_cache.as_secs_f64() / with_cache.as_secs_f64();
209 println!("📈 Speedup: {:.2}x faster", speedup);
210
211 Ok(())
212}More examples
6fn main() {
7 println!("\n🚀 JSON Evaluation - SPAJ Toggle Example\n");
8
9 let schema_path = Path::new("samples/spaj.json");
10 let schema_str = fs::read_to_string(schema_path).expect("Failed to read schema");
11
12 // Initial data with minimal context required
13 let context_str = json!({
14 "agentProfile": { "sob": "AG" }
15 })
16 .to_string();
17
18 let initial_data = json!({
19 "illustration": {
20 "basicinformation": {
21 "print_polflag": false
22 }
23 }
24 })
25 .to_string();
26
27 // Initialize logic
28 let mut eval = JSONEval::new(&schema_str, Some(&context_str), Some(&initial_data))
29 .expect("Failed to create JSONEval");
30
31 // Helper to check visibility
32 let check_visibility = |eval: &mut JSONEval, expected_hidden: bool, step: &str| {
33 let result = eval.get_evaluated_schema(false);
34 let hidden = result.pointer("/illustration/properties/basicinformation/properties/print_poladdress/condition/hidden")
35 .and_then(|v| v.as_bool());
36
37 match hidden {
38 Some(val) => {
39 if val == expected_hidden {
40 println!(
41 "✅ {}: Hidden = {} (Expected: {})",
42 step, val, expected_hidden
43 );
44 } else {
45 println!(
46 "❌ {}: Hidden = {} (Expected: {})",
47 step, val, expected_hidden
48 );
49 }
50 }
51 None => println!("❌ {}: 'hidden' property not found", step),
52 }
53 };
54
55 // Step 1: Initial state (false)
56 println!("Step 1: Initial State (print_polflag: false)");
57 eval.evaluate(&initial_data, Some(&context_str), None, None)
58 .expect("Evaluation failed");
59 check_visibility(&mut eval, true, "Initial check");
60
61 // Step 2: Toggle to true
62 println!("\nStep 2: Toggle True (print_polflag: true)");
63 let data_true = json!({
64 "illustration": {
65 "basicinformation": {
66 "print_polflag": true
67 }
68 }
69 })
70 .to_string();
71 eval.evaluate(&data_true, Some(&context_str), None, None)
72 .expect("Evaluation failed");
73 check_visibility(&mut eval, false, "Toggle ON check");
74
75 // Step 3: Toggle back to false
76 println!("\nStep 3: Toggle False (print_polflag: false)");
77 let data_false = json!({
78 "illustration": {
79 "basicinformation": {
80 "print_polflag": false
81 }
82 }
83 })
84 .to_string();
85 eval.evaluate(&data_false, Some(&context_str), None, None)
86 .expect("Evaluation failed");
87
88 let hidden_path =
89 "#/illustration/properties/basicinformation/properties/print_poladdress/condition/hidden";
90 if let Some(deps) = eval.dependencies.get(hidden_path) {
91 println!("Debug: Dependencies for hidden: {:?}", deps);
92 } else {
93 println!("Debug: No dependencies found for hidden path");
94 }
95
96 // Debug: Print current flag value
97 if let Some(val) = eval
98 .get_evaluated_schema(false)
99 .pointer("/illustration/properties/basicinformation/properties/print_polflag/value")
100 {
101 println!("Debug: print_polflag value is: {}", val);
102 }
103
104 check_visibility(&mut eval, true, "Toggle OFF check");
105}42fn main() {
43 let args: Vec<String> = std::env::args().collect();
44 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("basic_parsed");
45
46 let mut scenario_filter: Option<String> = None;
47 let mut enable_comparison = false;
48 let mut show_timing = false;
49 let mut i = 1;
50
51 // Parse arguments
52 while i < args.len() {
53 let arg = &args[i];
54
55 if arg == "-h" || arg == "--help" {
56 print_help(program_name);
57 return;
58 } else if arg == "--compare" {
59 enable_comparison = true;
60 } else if arg == "--timing" {
61 show_timing = true;
62 } else if !arg.starts_with('-') {
63 scenario_filter = Some(arg.clone());
64 } else {
65 eprintln!("Error: unknown option '{}'", arg);
66 print_help(program_name);
67 return;
68 }
69
70 i += 1;
71 }
72
73 println!("\n🚀 JSON Evaluation - Basic Example (Parsed / JSON & MsgPack)\n");
74 println!("📦 Using Arc<ParsedSchema> for efficient caching\n");
75
76 if enable_comparison {
77 println!("🔍 Comparison: enabled");
78 }
79 if show_timing {
80 println!("⏱️ Internal timing: enabled");
81 }
82 if enable_comparison || show_timing {
83 println!();
84 }
85
86 let samples_dir = Path::new("samples");
87 let mut scenarios = common::discover_scenarios(samples_dir);
88
89 // Filter scenarios if a filter is provided
90 if let Some(ref filter) = scenario_filter {
91 scenarios.retain(|s| s.name.contains(filter));
92 println!("📋 Filtering scenarios matching: '{}'\n", filter);
93 }
94
95 if scenarios.is_empty() {
96 if let Some(filter) = scenario_filter {
97 println!(
98 "ℹ️ No scenarios found matching '{}' in `{}`.",
99 filter,
100 samples_dir.display()
101 );
102 } else {
103 println!(
104 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
105 samples_dir.display()
106 );
107 }
108 return;
109 }
110
111 println!("📊 Found {} scenario(s)\n", scenarios.len());
112
113 let mut total_parse_time = std::time::Duration::ZERO;
114 let mut total_eval_time = std::time::Duration::ZERO;
115 let mut successful_scenarios = 0;
116 let mut comparison_failures = 0;
117
118 for scenario in &scenarios {
119 println!("==============================");
120 println!("Scenario: {}", scenario.name);
121 println!(
122 "Schema: {} ({})",
123 scenario.schema_path.display(),
124 if scenario.is_msgpack {
125 "MessagePack"
126 } else {
127 "JSON"
128 }
129 );
130 println!("Data: {}\n", scenario.data_path.display());
131
132 // Clear timing data from previous scenarios
133 if show_timing {
134 json_eval_rs::enable_timing();
135 json_eval_rs::clear_timing_data();
136 }
137
138 let data_str = fs::read_to_string(&scenario.data_path)
139 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
140
141 // Step 1: Parse schema once
142 let parse_start = Instant::now();
143 let parsed_schema = if scenario.is_msgpack {
144 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
145 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
146 });
147 println!(
148 " 📦 MessagePack schema size: {} bytes",
149 schema_msgpack.len()
150 );
151 Arc::new(
152 ParsedSchema::parse_msgpack(&schema_msgpack)
153 .unwrap_or_else(|e| panic!("failed to parse MessagePack schema: {}", e)),
154 )
155 } else {
156 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
157 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
158 });
159 Arc::new(
160 ParsedSchema::parse(&schema_str)
161 .unwrap_or_else(|e| panic!("failed to parse schema: {}", e)),
162 )
163 };
164 let parse_time = parse_start.elapsed();
165 println!(" 📝 Schema parsing: {:?}", parse_time);
166
167 // Step 2: Create JSONEval from ParsedSchema (reuses compiled logic)
168 let eval_start = Instant::now();
169 let mut eval = JSONEval::with_parsed_schema(
170 parsed_schema.clone(), // Arc::clone is cheap!
171 Some("{}"),
172 Some(&data_str),
173 )
174 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e));
175
176 eval.evaluate(&data_str, Some("{}"), None, None)
177 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
178
179 let evaluated_schema = eval.get_evaluated_schema(false);
180 let eval_time = eval_start.elapsed();
181
182 println!(" ⚡ Eval: {:?}", eval_time);
183 println!(" ⏱️ Total: {:?}\n", parse_time + eval_time);
184
185 // Print detailed timing breakdown if --timing flag is set
186 if show_timing {
187 json_eval_rs::print_timing_summary();
188 }
189
190 total_parse_time += parse_time;
191 total_eval_time += eval_time;
192 successful_scenarios += 1;
193
194 // Save results
195 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
196 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
197
198 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
199 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
200
201 let mut metadata_obj = Map::new();
202 metadata_obj.insert(
203 "dependencies".to_string(),
204 serde_json::to_value(&*eval.dependencies).unwrap(),
205 );
206 metadata_obj.insert(
207 "evaluations".to_string(),
208 serde_json::to_value(&*eval.evaluations).unwrap(),
209 );
210 metadata_obj.insert(
211 "sorted_evaluations".to_string(),
212 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
213 );
214
215 fs::write(
216 &parsed_path,
217 common::pretty_json(&Value::Object(metadata_obj)),
218 )
219 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
220
221 println!("✅ Results saved:");
222 println!(" - {}", evaluated_path.display());
223 println!(" - {}\n", parsed_path.display());
224
225 // Optional comparison
226 if enable_comparison {
227 if let Some(comp_path) = &scenario.comparison_path {
228 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
229 comparison_failures += 1;
230 }
231 println!();
232 }
233 }
234 }
235
236 // Print summary
237 println!("{}", "=".repeat(50));
238 println!("📊 Summary");
239 println!("{}", "=".repeat(50));
240 println!("Total scenarios run: {}", successful_scenarios);
241 println!("Total parsing time: {:?}", total_parse_time);
242 println!("Total evaluation time: {:?}", total_eval_time);
243 println!("Total time: {:?}", total_parse_time + total_eval_time);
244
245 if successful_scenarios > 1 {
246 println!("\nAverage per scenario:");
247 println!(
248 " Parsing: {:?}",
249 total_parse_time / successful_scenarios as u32
250 );
251 println!(
252 " Evaluation: {:?}",
253 total_eval_time / successful_scenarios as u32
254 );
255 }
256
257 if enable_comparison {
258 println!("\nComparison failures: {}", comparison_failures);
259 }
260
261 println!("\n✅ All scenarios completed!\n");
262}37fn main() {
38 let args: Vec<String> = std::env::args().collect();
39 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("basic");
40
41 let mut scenario_filter: Option<String> = None;
42 let mut enable_comparison = false;
43 let mut show_timing = false;
44 let mut i = 1;
45
46 // Parse arguments
47 while i < args.len() {
48 let arg = &args[i];
49
50 if arg == "-h" || arg == "--help" {
51 print_help(program_name);
52 return;
53 } else if arg == "--compare" {
54 enable_comparison = true;
55 } else if arg == "--timing" {
56 show_timing = true;
57 } else if !arg.starts_with('-') {
58 scenario_filter = Some(arg.clone());
59 } else {
60 eprintln!("Error: unknown option '{}'", arg);
61 print_help(program_name);
62 return;
63 }
64
65 i += 1;
66 }
67
68 println!("\n🚀 JSON Evaluation - Basic Example (JSON/MsgPack Schema)\n");
69
70 if enable_comparison {
71 println!("🔍 Comparison: enabled");
72 }
73 if show_timing {
74 println!("⏱️ Internal timing: enabled");
75 }
76 if enable_comparison || show_timing {
77 println!();
78 }
79
80 let samples_dir = Path::new("samples");
81 let mut scenarios = common::discover_scenarios(samples_dir);
82
83 // Filter scenarios if a filter is provided
84 if let Some(ref filter) = scenario_filter {
85 scenarios.retain(|s| s.name.contains(filter));
86 println!("📋 Filtering scenarios matching: '{}'\n", filter);
87 }
88
89 if scenarios.is_empty() {
90 if let Some(filter) = scenario_filter {
91 println!(
92 "ℹ️ No scenarios found matching '{}' in `{}`.",
93 filter,
94 samples_dir.display()
95 );
96 } else {
97 println!(
98 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
99 samples_dir.display()
100 );
101 }
102 return;
103 }
104
105 println!("📊 Found {} scenario(s)\n", scenarios.len());
106
107 let mut total_parse_time = std::time::Duration::ZERO;
108 let mut total_eval_time = std::time::Duration::ZERO;
109 let mut successful_scenarios = 0;
110 let mut comparison_failures = 0;
111
112 for scenario in &scenarios {
113 println!("==============================");
114 println!("Scenario: {}", scenario.name);
115 println!(
116 "Schema: {} ({})",
117 scenario.schema_path.display(),
118 if scenario.is_msgpack {
119 "MessagePack"
120 } else {
121 "JSON"
122 }
123 );
124 println!("Data: {}\n", scenario.data_path.display());
125
126 // Clear timing data from previous scenarios
127 if show_timing {
128 json_eval_rs::enable_timing();
129 json_eval_rs::clear_timing_data();
130 }
131
132 let data_str = fs::read_to_string(&scenario.data_path)
133 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
134
135 // Step 1: Parse schema
136 let parse_start = Instant::now();
137
138 let mut eval = if scenario.is_msgpack {
139 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
140 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
141 });
142 println!(
143 " 📦 MessagePack schema size: {} bytes",
144 schema_msgpack.len()
145 );
146 JSONEval::new_from_msgpack(&schema_msgpack, None, Some(&data_str))
147 .unwrap_or_else(|e| panic!("failed to create JSONEval from MessagePack: {}", e))
148 } else {
149 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
150 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
151 });
152 JSONEval::new(&schema_str, None, Some(&data_str))
153 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
154 };
155
156 let parse_time = parse_start.elapsed();
157 println!(" 📝 Parse (new): {:?}", parse_time);
158
159 // Step 2: Evaluate
160 let eval_start = Instant::now();
161
162 eval.evaluate(&data_str, Some("{}"), None, None)
163 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
164
165 // Step 3: Validate
166 let validation_start = Instant::now();
167 let validation_result = eval
168 .validate(&data_str, None, None, None)
169 .unwrap_or_else(|e| panic!("validation failed: {}", e));
170 let validation_time = validation_start.elapsed();
171 println!(" 🛡️ Validate: {:?}", validation_time);
172
173 // Legacy behavior: get_evaluated_schema takes skip_layout: bool
174 // We pass false to ensure layout IS resolved
175 let evaluated_schema = eval.get_evaluated_schema(false);
176 let schema_value = eval.get_schema_value();
177 let eval_time = eval_start.elapsed();
178
179 println!(" ⚡ Eval: {:?}", eval_time);
180 println!(" ⏱️ Total: {:?}\n", parse_time + eval_time);
181
182 // Print detailed timing breakdown if --timing flag is set
183 if show_timing {
184 json_eval_rs::print_timing_summary();
185 }
186
187 total_parse_time += parse_time;
188 total_eval_time += eval_time;
189 successful_scenarios += 1;
190
191 // Save results
192 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
193 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
194 let value_path = samples_dir.join(format!("{}-schema-value.json", scenario.name));
195 let validation_path = samples_dir.join(format!("{}-validation.json", scenario.name));
196
197 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
198 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
199
200 let mut metadata_obj = Map::new();
201 metadata_obj.insert(
202 "dependencies".to_string(),
203 serde_json::to_value(&*eval.dependencies).unwrap(),
204 );
205 metadata_obj.insert(
206 "evaluations".to_string(),
207 serde_json::to_value(&*eval.evaluations).unwrap(),
208 );
209 metadata_obj.insert(
210 "sorted_evaluations".to_string(),
211 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
212 );
213
214 fs::write(
215 &parsed_path,
216 common::pretty_json(&Value::Object(metadata_obj)),
217 )
218 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
219
220 fs::write(&value_path, common::pretty_json(&schema_value))
221 .unwrap_or_else(|e| panic!("failed to write {}: {}", value_path.display(), e));
222
223 let validation_value = serde_json::to_value(&validation_result)
224 .unwrap_or_else(|e| panic!("failed to serialize validation result: {}", e));
225 fs::write(&validation_path, common::pretty_json(&validation_value))
226 .unwrap_or_else(|e| panic!("failed to write {}: {}", validation_path.display(), e));
227
228 println!("✅ Results saved:");
229 println!(" - {}", evaluated_path.display());
230 println!(" - {}", parsed_path.display());
231 println!(" - {}", value_path.display());
232 println!(" - {}\n", validation_path.display());
233
234 // Optional comparison
235 if enable_comparison {
236 if let Some(comp_path) = &scenario.comparison_path {
237 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
238 comparison_failures += 1;
239 }
240 println!();
241 }
242 }
243 }
244
245 // Print summary
246 println!("{}", "=".repeat(50));
247 println!("📊 Summary");
248 println!("{}", "=".repeat(50));
249 println!("Total scenarios run: {}", successful_scenarios);
250 println!("Total parse time: {:?}", total_parse_time);
251 println!("Total eval time: {:?}", total_eval_time);
252 println!("Total time: {:?}", total_parse_time + total_eval_time);
253
254 if successful_scenarios > 1 {
255 println!("\nAverage per scenario:");
256 println!(
257 " Parse: {:?}",
258 total_parse_time / successful_scenarios as u32
259 );
260 println!(
261 " Eval: {:?}",
262 total_eval_time / successful_scenarios as u32
263 );
264 }
265
266 if enable_comparison {
267 println!("Comparison failures: {}", comparison_failures);
268 }
269
270 println!("\n✅ All scenarios completed!\n");
271}44fn main() {
45 let args: Vec<String> = std::env::args().collect();
46 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("benchmark");
47
48 let mut iterations = 1usize;
49 let mut scenario_filter: Option<String> = None;
50 let mut show_cpu_info = false;
51 let mut use_parsed_schema = false;
52 let mut use_cache = false;
53 let mut concurrent_count: Option<usize> = None;
54 let mut enable_comparison = false;
55 let mut show_timing = false;
56 let mut i = 1;
57
58 // Parse arguments
59 while i < args.len() {
60 let arg = &args[i];
61
62 if arg == "-h" || arg == "--help" {
63 print_help(program_name);
64 return;
65 } else if arg == "--cpu-info" {
66 show_cpu_info = true;
67 } else if arg == "--parsed" {
68 use_parsed_schema = true;
69 } else if arg == "--cache" {
70 use_cache = true;
71 } else if arg == "--compare" {
72 enable_comparison = true;
73 } else if arg == "--timing" {
74 show_timing = true;
75 } else if arg == "--concurrent" {
76 if i + 1 >= args.len() {
77 eprintln!("Error: {} requires a value", arg);
78 print_help(program_name);
79 return;
80 }
81 i += 1;
82 match args[i].parse::<usize>() {
83 Ok(n) if n > 0 => concurrent_count = Some(n),
84 _ => {
85 eprintln!(
86 "Error: concurrent count must be a positive integer, got '{}'",
87 args[i]
88 );
89 return;
90 }
91 }
92 } else if arg == "-i" || arg == "--iterations" {
93 if i + 1 >= args.len() {
94 eprintln!("Error: {} requires a value", arg);
95 print_help(program_name);
96 return;
97 }
98 i += 1;
99 match args[i].parse::<usize>() {
100 Ok(n) if n > 0 => iterations = n,
101 _ => {
102 eprintln!(
103 "Error: iterations must be a positive integer, got '{}'",
104 args[i]
105 );
106 return;
107 }
108 }
109 } else if !arg.starts_with('-') {
110 scenario_filter = Some(arg.clone());
111 } else {
112 eprintln!("Error: unknown option '{}'", arg);
113 print_help(program_name);
114 return;
115 }
116
117 i += 1;
118 }
119
120 println!("\n🚀 JSON Evaluation - Benchmark\n");
121
122 // Show CPU info if requested or if running benchmarks
123 if show_cpu_info || iterations > 1 || concurrent_count.is_some() {
124 common::print_cpu_info();
125 }
126
127 if use_parsed_schema {
128 println!("📦 Mode: ParsedSchema (parse once, reuse for all iterations)\n");
129 }
130
131 if use_cache {
132 println!("♻️ Mode: Cache (reuse JSONEval instance across iterations)\n");
133 }
134
135 if let Some(count) = concurrent_count {
136 println!("🔀 Concurrent evaluations: {} threads\n", count);
137 } else if iterations > 1 {
138 println!("🔄 Iterations per scenario: {}\n", iterations);
139 }
140
141 if enable_comparison {
142 println!("🔍 Comparison: enabled");
143 }
144 if show_timing {
145 println!("⏱️ Internal timing: enabled");
146 }
147 if enable_comparison || show_timing {
148 println!();
149 }
150
151 let samples_dir = Path::new("samples");
152 let mut scenarios = common::discover_scenarios(samples_dir);
153
154 // Filter scenarios if a filter is provided
155 if let Some(ref filter) = scenario_filter {
156 scenarios.retain(|s| s.name.contains(filter));
157 println!("📋 Filtering scenarios matching: '{}'\n", filter);
158 }
159
160 if scenarios.is_empty() {
161 if let Some(filter) = scenario_filter {
162 println!(
163 "ℹ️ No scenarios found matching '{}' in `{}`.",
164 filter,
165 samples_dir.display()
166 );
167 } else {
168 println!(
169 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
170 samples_dir.display()
171 );
172 }
173 return;
174 }
175
176 println!("📊 Found {} scenario(s)\n", scenarios.len());
177
178 let mut total_parse_time = std::time::Duration::ZERO;
179 let mut total_eval_time = std::time::Duration::ZERO;
180 let mut successful_scenarios = 0;
181 let mut comparison_failures = 0;
182
183 for scenario in &scenarios {
184 println!("==============================");
185 println!("Scenario: {}", scenario.name);
186 println!(
187 "Schema: {} ({})",
188 scenario.schema_path.display(),
189 if scenario.is_msgpack {
190 "MessagePack"
191 } else {
192 "JSON"
193 }
194 );
195 println!("Data: {}\n", scenario.data_path.display());
196
197 // Clear timing data from previous scenarios
198 if show_timing {
199 json_eval_rs::enable_timing();
200 json_eval_rs::clear_timing_data();
201 }
202
203 let data_str = fs::read_to_string(&scenario.data_path)
204 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
205
206 println!("Running evaluation...\n");
207
208 let (parse_time, eval_time, evaluated_schema, eval, iteration_times) = if use_parsed_schema
209 {
210 // ParsedSchema mode: parse once, reuse for all iterations/threads
211 let start_time = Instant::now();
212
213 let parsed_schema = if scenario.is_msgpack {
214 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
215 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
216 });
217 println!(
218 " 📦 MessagePack schema size: {} bytes",
219 schema_msgpack.len()
220 );
221 Arc::new(
222 ParsedSchema::parse_msgpack(&schema_msgpack)
223 .unwrap_or_else(|e| panic!("failed to parse MessagePack schema: {}", e)),
224 )
225 } else {
226 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
227 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
228 });
229 Arc::new(
230 ParsedSchema::parse(&schema_str)
231 .unwrap_or_else(|e| panic!("failed to parse schema: {}", e)),
232 )
233 };
234
235 let parse_time = start_time.elapsed();
236 println!(" Schema parsing & compilation: {:?}", parse_time);
237
238 // Concurrent mode with ParsedSchema
239 if let Some(thread_count) = concurrent_count {
240 use std::thread;
241
242 let eval_start = Instant::now();
243 let mut handles = vec![];
244
245 for thread_id in 0..thread_count {
246 let parsed_clone = parsed_schema.clone();
247 let data_str_clone = data_str.clone();
248 let iter_count = iterations;
249 let thread_use_cache = use_cache;
250
251 let handle = thread::spawn(move || {
252 let mut thread_times = Vec::with_capacity(iter_count);
253 let mut last_schema = Value::Null;
254
255 let mut eval_instance = JSONEval::with_parsed_schema(
256 parsed_clone.clone(),
257 Some("{}"),
258 Some(&data_str_clone),
259 )
260 .unwrap();
261
262 for iter in 0..iter_count {
263 let iter_start = Instant::now();
264
265 if !thread_use_cache && iter > 0 {
266 eval_instance = JSONEval::with_parsed_schema(
267 parsed_clone.clone(),
268 Some("{}"),
269 Some(&data_str_clone),
270 )
271 .unwrap();
272 }
273
274 eval_instance
275 .evaluate(&data_str_clone, Some("{}"), None, None)
276 .unwrap();
277 last_schema = eval_instance.get_evaluated_schema(false);
278 thread_times.push(iter_start.elapsed());
279 }
280
281 (thread_times, last_schema, thread_id)
282 });
283 handles.push(handle);
284 }
285
286 let mut all_iteration_times = Vec::new();
287 let mut evaluated_schema = Value::Null;
288
289 for handle in handles {
290 let (thread_times, thread_schema, thread_id) = handle.join().unwrap();
291 println!(
292 " Thread {} completed {} iterations",
293 thread_id,
294 thread_times.len()
295 );
296 all_iteration_times.extend(thread_times);
297 evaluated_schema = thread_schema; // Use last thread's result
298 }
299
300 let eval_time = eval_start.elapsed();
301
302 // Create a temp eval for metadata export
303 let temp_eval = JSONEval::with_parsed_schema(
304 parsed_schema.clone(),
305 Some("{}"),
306 Some(&data_str),
307 )
308 .unwrap();
309
310 (
311 parse_time,
312 eval_time,
313 evaluated_schema,
314 temp_eval,
315 all_iteration_times,
316 )
317 } else {
318 // Sequential iterations with ParsedSchema
319 let eval_start = Instant::now();
320 let mut evaluated_schema = Value::Null;
321 let mut iteration_times = Vec::with_capacity(iterations);
322 let mut eval_instance = JSONEval::with_parsed_schema(
323 parsed_schema.clone(),
324 Some("{}"),
325 Some(&data_str),
326 )
327 .unwrap();
328
329 for iter in 0..iterations {
330 let iter_start = Instant::now();
331
332 if !use_cache && iter > 0 {
333 eval_instance = JSONEval::with_parsed_schema(
334 parsed_schema.clone(),
335 Some("{}"),
336 Some(&data_str),
337 )
338 .unwrap();
339 }
340
341 eval_instance
342 .evaluate(&data_str, Some("{}"), None, None)
343 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
344 evaluated_schema = eval_instance.get_evaluated_schema(false);
345 iteration_times.push(iter_start.elapsed());
346
347 if iterations > 1 && (iter + 1) % 10 == 0 {
348 print!(".");
349 if (iter + 1) % 50 == 0 {
350 println!(" {}/{}", iter + 1, iterations);
351 }
352 }
353 }
354
355 if iterations > 1 && iterations % 50 != 0 {
356 println!(" {}/{}", iterations, iterations);
357 }
358
359 let eval_time = eval_start.elapsed();
360 (
361 parse_time,
362 eval_time,
363 evaluated_schema,
364 eval_instance,
365 iteration_times,
366 )
367 }
368 } else {
369 // Traditional mode: parse and create JSONEval each time
370 let schema_msgpack = if scenario.is_msgpack {
371 let bytes = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
372 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
373 });
374 println!(" 📦 MessagePack schema size: {} bytes", bytes.len());
375 Some(bytes)
376 } else {
377 None
378 };
379
380 let schema_str = if !scenario.is_msgpack {
381 Some(
382 fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
383 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
384 }),
385 )
386 } else {
387 None
388 };
389
390 let start_time = Instant::now();
391 let mut eval = if scenario.is_msgpack {
392 JSONEval::new_from_msgpack(schema_msgpack.as_ref().unwrap(), None, Some(&data_str))
393 .unwrap_or_else(|e| panic!("failed to create JSONEval from MessagePack: {}", e))
394 } else {
395 JSONEval::new(schema_str.as_ref().unwrap(), None, Some(&data_str))
396 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
397 };
398 let parse_time = start_time.elapsed();
399 println!(" Schema parsing & compilation: {:?}", parse_time);
400
401 let eval_start = Instant::now();
402 let mut evaluated_schema = Value::Null;
403 let mut iteration_times = Vec::with_capacity(iterations);
404
405 for iter in 0..iterations {
406 let iter_start = Instant::now();
407
408 if !use_cache && iter > 0 {
409 eval = if scenario.is_msgpack {
410 JSONEval::new_from_msgpack(
411 schema_msgpack.as_ref().unwrap(),
412 None,
413 Some(&data_str),
414 )
415 .unwrap_or_else(|e| {
416 panic!("failed to create JSONEval from MessagePack: {}", e)
417 })
418 } else {
419 JSONEval::new(schema_str.as_ref().unwrap(), None, Some(&data_str))
420 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
421 };
422 }
423
424 eval.evaluate(&data_str, Some("{}"), None, None)
425 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
426 evaluated_schema = eval.get_evaluated_schema(false);
427 iteration_times.push(iter_start.elapsed());
428
429 if iterations > 1 && (iter + 1) % 10 == 0 {
430 print!(".");
431 if (iter + 1) % 50 == 0 {
432 println!(" {}/{}", iter + 1, iterations);
433 }
434 }
435 }
436
437 if iterations > 1 && iterations % 50 != 0 {
438 println!(" {}/{}", iterations, iterations);
439 }
440
441 let eval_time = eval_start.elapsed();
442 (
443 parse_time,
444 eval_time,
445 evaluated_schema,
446 eval,
447 iteration_times,
448 )
449 };
450
451 // Calculate statistics
452 let total_iterations = iteration_times.len();
453 if total_iterations == 1 {
454 println!(" Evaluation: {:?}", eval_time);
455 } else {
456 let avg_time = eval_time / total_iterations as u32;
457 let min_time = iteration_times.iter().min().unwrap();
458 let max_time = iteration_times.iter().max().unwrap();
459
460 println!(" Total evaluation time: {:?}", eval_time);
461 println!(" Total iterations: {}", total_iterations);
462 println!(" Average per iteration: {:?}", avg_time);
463 println!(" Min: {:?} | Max: {:?}", min_time, max_time);
464 }
465
466 let total_time = parse_time + eval_time;
467 println!("⏱️ Execution time: {:?}\n", total_time);
468
469 // Print detailed timing breakdown if --timing flag is set
470 if show_timing {
471 json_eval_rs::print_timing_summary();
472 }
473
474 // Track statistics
475 total_parse_time += parse_time;
476 total_eval_time += eval_time;
477 successful_scenarios += 1;
478
479 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
480 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
481
482 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
483 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
484
485 let mut metadata_obj = Map::new();
486 metadata_obj.insert(
487 "dependencies".to_string(),
488 serde_json::to_value(&*eval.dependencies).unwrap(),
489 );
490 metadata_obj.insert(
491 "sorted_evaluations".to_string(),
492 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
493 );
494
495 fs::write(
496 &parsed_path,
497 common::pretty_json(&Value::Object(metadata_obj)),
498 )
499 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
500
501 println!("✅ Results saved:");
502 println!(" - {}", evaluated_path.display());
503 println!(" - {}\n", parsed_path.display());
504
505 // Optional comparison
506 if enable_comparison {
507 if let Some(comp_path) = &scenario.comparison_path {
508 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
509 comparison_failures += 1;
510 }
511 println!();
512 }
513 }
514 }
515
516 // Print summary statistics
517 if successful_scenarios > 0 {
518 println!("\n{}", "=".repeat(50));
519 println!("📊 Summary Statistics");
520 println!("{}", "=".repeat(50));
521 println!("Total scenarios run: {}", successful_scenarios);
522 println!("Total parsing time: {:?}", total_parse_time);
523 println!("Total evaluation time: {:?}", total_eval_time);
524 println!("Total time: {:?}", total_parse_time + total_eval_time);
525
526 if successful_scenarios > 1 {
527 println!("\nAverage per scenario:");
528 println!(
529 " Parsing: {:?}",
530 total_parse_time / successful_scenarios as u32
531 );
532 println!(
533 " Evaluation: {:?}",
534 total_eval_time / successful_scenarios as u32
535 );
536 }
537
538 if enable_comparison {
539 println!("\nComparison failures: {}", comparison_failures);
540 }
541
542 println!("\n✅ All scenarios completed successfully!\n");
543 }
544}Source§impl JSONEval
impl JSONEval
Sourcepub fn get_evaluated_schema(&mut self, skip_layout: bool) -> Value
pub fn get_evaluated_schema(&mut self, skip_layout: bool) -> Value
Get the evaluated schema with optional layout resolution.
§Arguments
skip_layout- Whether to skip layout resolution.
§Returns
The evaluated schema as a JSON value, with all $static_array markers resolved
to their actual evaluated data.
Examples found in repository?
37fn demo_local_cache() -> Result<(), Box<dyn std::error::Error>> {
38 println!("📦 Example 1: Local Cache Instance");
39 println!("Creating a dedicated cache for this application...\n");
40
41 let cache = ParsedSchemaCache::new();
42
43 // Simple schema
44 let schema_json = r#"{
45 "$params": {
46 "rate": { "type": "number" }
47 },
48 "result": {
49 "type": "number",
50 "title": "Calculated Result",
51 "$evaluation": {
52 "logic": { "*": [{"var": "$rate"}, 100] }
53 }
54 }
55 }"#;
56
57 // Parse and cache with a custom key
58 println!("📝 Parsing schema and caching with key 'calculation-v1'...");
59 let parsed = ParsedSchema::parse(schema_json)?;
60 cache.insert("calculation-v1".to_string(), Arc::new(parsed));
61
62 println!("✅ Schema cached successfully");
63 println!(" Cache size: {} entries", cache.len());
64 println!(" Keys: {:?}\n", cache.keys());
65
66 // Retrieve and use cached schema
67 println!("🔍 Retrieving cached schema...");
68 if let Some(cached_schema) = cache.get("calculation-v1") {
69 println!("✅ Retrieved from cache");
70
71 // Create JSONEval from cached ParsedSchema
72 let mut eval = JSONEval::with_parsed_schema(cached_schema, Some(r#"{"rate": 1.5}"#), None)?;
73 eval.evaluate("{}", None, None, None)?;
74
75 let evaluated = eval.get_evaluated_schema(false);
76 let result = evaluated
77 .pointer("/result")
78 .and_then(|v| v.as_f64())
79 .unwrap_or(0.0);
80 println!(" Evaluation result: {}\n", result);
81 }
82
83 // Check cache stats
84 let stats = cache.stats();
85 println!("📊 Cache Statistics: {}", stats);
86
87 // Remove entry
88 println!("\n🗑️ Removing 'calculation-v1' from cache...");
89 cache.remove("calculation-v1");
90 println!(" Cache size after removal: {}", cache.len());
91
92 Ok(())
93}
94
95fn demo_global_cache() -> Result<(), Box<dyn std::error::Error>> {
96 println!("🌍 Example 2: Global Cache Instance");
97 println!("Using the built-in PARSED_SCHEMA_CACHE...\n");
98
99 let schema_json = r#"{
100 "$params": {
101 "x": { "type": "number" },
102 "y": { "type": "number" }
103 },
104 "sum": {
105 "type": "number",
106 "$evaluation": { "+": [{"var": "$x"}, {"var": "$y"}] }
107 }
108 }"#;
109
110 // Use global cache
111 println!("📝 Caching schema globally with key 'math-operations'...");
112 let parsed = ParsedSchema::parse(schema_json)?;
113 PARSED_SCHEMA_CACHE.insert("math-operations".to_string(), Arc::new(parsed));
114
115 println!("✅ Schema cached globally");
116 println!(" Global cache size: {}\n", PARSED_SCHEMA_CACHE.len());
117
118 // Access from anywhere in the application
119 simulate_another_function()?;
120
121 // Clean up
122 println!("\n🧹 Clearing global cache...");
123 PARSED_SCHEMA_CACHE.clear();
124 println!(" Global cache size: {}", PARSED_SCHEMA_CACHE.len());
125
126 Ok(())
127}
128
129fn simulate_another_function() -> Result<(), Box<dyn std::error::Error>> {
130 println!("🔄 In another function, accessing global cache...");
131
132 if let Some(cached) = PARSED_SCHEMA_CACHE.get("math-operations") {
133 println!("✅ Retrieved schema from global cache");
134
135 let mut eval = JSONEval::with_parsed_schema(cached, Some(r#"{"x": 10, "y": 20}"#), None)?;
136 eval.evaluate("{}", None, None, None)?;
137
138 let evaluated = eval.get_evaluated_schema(false);
139 let sum = evaluated
140 .pointer("/sum")
141 .and_then(|v| v.as_f64())
142 .unwrap_or(0.0);
143 println!(" Result: {}", sum);
144 }
145
146 Ok(())
147}More examples
6fn main() {
7 println!("\n🚀 JSON Evaluation - SPAJ Toggle Example\n");
8
9 let schema_path = Path::new("samples/spaj.json");
10 let schema_str = fs::read_to_string(schema_path).expect("Failed to read schema");
11
12 // Initial data with minimal context required
13 let context_str = json!({
14 "agentProfile": { "sob": "AG" }
15 })
16 .to_string();
17
18 let initial_data = json!({
19 "illustration": {
20 "basicinformation": {
21 "print_polflag": false
22 }
23 }
24 })
25 .to_string();
26
27 // Initialize logic
28 let mut eval = JSONEval::new(&schema_str, Some(&context_str), Some(&initial_data))
29 .expect("Failed to create JSONEval");
30
31 // Helper to check visibility
32 let check_visibility = |eval: &mut JSONEval, expected_hidden: bool, step: &str| {
33 let result = eval.get_evaluated_schema(false);
34 let hidden = result.pointer("/illustration/properties/basicinformation/properties/print_poladdress/condition/hidden")
35 .and_then(|v| v.as_bool());
36
37 match hidden {
38 Some(val) => {
39 if val == expected_hidden {
40 println!(
41 "✅ {}: Hidden = {} (Expected: {})",
42 step, val, expected_hidden
43 );
44 } else {
45 println!(
46 "❌ {}: Hidden = {} (Expected: {})",
47 step, val, expected_hidden
48 );
49 }
50 }
51 None => println!("❌ {}: 'hidden' property not found", step),
52 }
53 };
54
55 // Step 1: Initial state (false)
56 println!("Step 1: Initial State (print_polflag: false)");
57 eval.evaluate(&initial_data, Some(&context_str), None, None)
58 .expect("Evaluation failed");
59 check_visibility(&mut eval, true, "Initial check");
60
61 // Step 2: Toggle to true
62 println!("\nStep 2: Toggle True (print_polflag: true)");
63 let data_true = json!({
64 "illustration": {
65 "basicinformation": {
66 "print_polflag": true
67 }
68 }
69 })
70 .to_string();
71 eval.evaluate(&data_true, Some(&context_str), None, None)
72 .expect("Evaluation failed");
73 check_visibility(&mut eval, false, "Toggle ON check");
74
75 // Step 3: Toggle back to false
76 println!("\nStep 3: Toggle False (print_polflag: false)");
77 let data_false = json!({
78 "illustration": {
79 "basicinformation": {
80 "print_polflag": false
81 }
82 }
83 })
84 .to_string();
85 eval.evaluate(&data_false, Some(&context_str), None, None)
86 .expect("Evaluation failed");
87
88 let hidden_path =
89 "#/illustration/properties/basicinformation/properties/print_poladdress/condition/hidden";
90 if let Some(deps) = eval.dependencies.get(hidden_path) {
91 println!("Debug: Dependencies for hidden: {:?}", deps);
92 } else {
93 println!("Debug: No dependencies found for hidden path");
94 }
95
96 // Debug: Print current flag value
97 if let Some(val) = eval
98 .get_evaluated_schema(false)
99 .pointer("/illustration/properties/basicinformation/properties/print_polflag/value")
100 {
101 println!("Debug: print_polflag value is: {}", val);
102 }
103
104 check_visibility(&mut eval, true, "Toggle OFF check");
105}42fn main() {
43 let args: Vec<String> = std::env::args().collect();
44 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("basic_parsed");
45
46 let mut scenario_filter: Option<String> = None;
47 let mut enable_comparison = false;
48 let mut show_timing = false;
49 let mut i = 1;
50
51 // Parse arguments
52 while i < args.len() {
53 let arg = &args[i];
54
55 if arg == "-h" || arg == "--help" {
56 print_help(program_name);
57 return;
58 } else if arg == "--compare" {
59 enable_comparison = true;
60 } else if arg == "--timing" {
61 show_timing = true;
62 } else if !arg.starts_with('-') {
63 scenario_filter = Some(arg.clone());
64 } else {
65 eprintln!("Error: unknown option '{}'", arg);
66 print_help(program_name);
67 return;
68 }
69
70 i += 1;
71 }
72
73 println!("\n🚀 JSON Evaluation - Basic Example (Parsed / JSON & MsgPack)\n");
74 println!("📦 Using Arc<ParsedSchema> for efficient caching\n");
75
76 if enable_comparison {
77 println!("🔍 Comparison: enabled");
78 }
79 if show_timing {
80 println!("⏱️ Internal timing: enabled");
81 }
82 if enable_comparison || show_timing {
83 println!();
84 }
85
86 let samples_dir = Path::new("samples");
87 let mut scenarios = common::discover_scenarios(samples_dir);
88
89 // Filter scenarios if a filter is provided
90 if let Some(ref filter) = scenario_filter {
91 scenarios.retain(|s| s.name.contains(filter));
92 println!("📋 Filtering scenarios matching: '{}'\n", filter);
93 }
94
95 if scenarios.is_empty() {
96 if let Some(filter) = scenario_filter {
97 println!(
98 "ℹ️ No scenarios found matching '{}' in `{}`.",
99 filter,
100 samples_dir.display()
101 );
102 } else {
103 println!(
104 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
105 samples_dir.display()
106 );
107 }
108 return;
109 }
110
111 println!("📊 Found {} scenario(s)\n", scenarios.len());
112
113 let mut total_parse_time = std::time::Duration::ZERO;
114 let mut total_eval_time = std::time::Duration::ZERO;
115 let mut successful_scenarios = 0;
116 let mut comparison_failures = 0;
117
118 for scenario in &scenarios {
119 println!("==============================");
120 println!("Scenario: {}", scenario.name);
121 println!(
122 "Schema: {} ({})",
123 scenario.schema_path.display(),
124 if scenario.is_msgpack {
125 "MessagePack"
126 } else {
127 "JSON"
128 }
129 );
130 println!("Data: {}\n", scenario.data_path.display());
131
132 // Clear timing data from previous scenarios
133 if show_timing {
134 json_eval_rs::enable_timing();
135 json_eval_rs::clear_timing_data();
136 }
137
138 let data_str = fs::read_to_string(&scenario.data_path)
139 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
140
141 // Step 1: Parse schema once
142 let parse_start = Instant::now();
143 let parsed_schema = if scenario.is_msgpack {
144 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
145 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
146 });
147 println!(
148 " 📦 MessagePack schema size: {} bytes",
149 schema_msgpack.len()
150 );
151 Arc::new(
152 ParsedSchema::parse_msgpack(&schema_msgpack)
153 .unwrap_or_else(|e| panic!("failed to parse MessagePack schema: {}", e)),
154 )
155 } else {
156 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
157 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
158 });
159 Arc::new(
160 ParsedSchema::parse(&schema_str)
161 .unwrap_or_else(|e| panic!("failed to parse schema: {}", e)),
162 )
163 };
164 let parse_time = parse_start.elapsed();
165 println!(" 📝 Schema parsing: {:?}", parse_time);
166
167 // Step 2: Create JSONEval from ParsedSchema (reuses compiled logic)
168 let eval_start = Instant::now();
169 let mut eval = JSONEval::with_parsed_schema(
170 parsed_schema.clone(), // Arc::clone is cheap!
171 Some("{}"),
172 Some(&data_str),
173 )
174 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e));
175
176 eval.evaluate(&data_str, Some("{}"), None, None)
177 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
178
179 let evaluated_schema = eval.get_evaluated_schema(false);
180 let eval_time = eval_start.elapsed();
181
182 println!(" ⚡ Eval: {:?}", eval_time);
183 println!(" ⏱️ Total: {:?}\n", parse_time + eval_time);
184
185 // Print detailed timing breakdown if --timing flag is set
186 if show_timing {
187 json_eval_rs::print_timing_summary();
188 }
189
190 total_parse_time += parse_time;
191 total_eval_time += eval_time;
192 successful_scenarios += 1;
193
194 // Save results
195 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
196 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
197
198 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
199 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
200
201 let mut metadata_obj = Map::new();
202 metadata_obj.insert(
203 "dependencies".to_string(),
204 serde_json::to_value(&*eval.dependencies).unwrap(),
205 );
206 metadata_obj.insert(
207 "evaluations".to_string(),
208 serde_json::to_value(&*eval.evaluations).unwrap(),
209 );
210 metadata_obj.insert(
211 "sorted_evaluations".to_string(),
212 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
213 );
214
215 fs::write(
216 &parsed_path,
217 common::pretty_json(&Value::Object(metadata_obj)),
218 )
219 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
220
221 println!("✅ Results saved:");
222 println!(" - {}", evaluated_path.display());
223 println!(" - {}\n", parsed_path.display());
224
225 // Optional comparison
226 if enable_comparison {
227 if let Some(comp_path) = &scenario.comparison_path {
228 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
229 comparison_failures += 1;
230 }
231 println!();
232 }
233 }
234 }
235
236 // Print summary
237 println!("{}", "=".repeat(50));
238 println!("📊 Summary");
239 println!("{}", "=".repeat(50));
240 println!("Total scenarios run: {}", successful_scenarios);
241 println!("Total parsing time: {:?}", total_parse_time);
242 println!("Total evaluation time: {:?}", total_eval_time);
243 println!("Total time: {:?}", total_parse_time + total_eval_time);
244
245 if successful_scenarios > 1 {
246 println!("\nAverage per scenario:");
247 println!(
248 " Parsing: {:?}",
249 total_parse_time / successful_scenarios as u32
250 );
251 println!(
252 " Evaluation: {:?}",
253 total_eval_time / successful_scenarios as u32
254 );
255 }
256
257 if enable_comparison {
258 println!("\nComparison failures: {}", comparison_failures);
259 }
260
261 println!("\n✅ All scenarios completed!\n");
262}37fn main() {
38 let args: Vec<String> = std::env::args().collect();
39 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("basic");
40
41 let mut scenario_filter: Option<String> = None;
42 let mut enable_comparison = false;
43 let mut show_timing = false;
44 let mut i = 1;
45
46 // Parse arguments
47 while i < args.len() {
48 let arg = &args[i];
49
50 if arg == "-h" || arg == "--help" {
51 print_help(program_name);
52 return;
53 } else if arg == "--compare" {
54 enable_comparison = true;
55 } else if arg == "--timing" {
56 show_timing = true;
57 } else if !arg.starts_with('-') {
58 scenario_filter = Some(arg.clone());
59 } else {
60 eprintln!("Error: unknown option '{}'", arg);
61 print_help(program_name);
62 return;
63 }
64
65 i += 1;
66 }
67
68 println!("\n🚀 JSON Evaluation - Basic Example (JSON/MsgPack Schema)\n");
69
70 if enable_comparison {
71 println!("🔍 Comparison: enabled");
72 }
73 if show_timing {
74 println!("⏱️ Internal timing: enabled");
75 }
76 if enable_comparison || show_timing {
77 println!();
78 }
79
80 let samples_dir = Path::new("samples");
81 let mut scenarios = common::discover_scenarios(samples_dir);
82
83 // Filter scenarios if a filter is provided
84 if let Some(ref filter) = scenario_filter {
85 scenarios.retain(|s| s.name.contains(filter));
86 println!("📋 Filtering scenarios matching: '{}'\n", filter);
87 }
88
89 if scenarios.is_empty() {
90 if let Some(filter) = scenario_filter {
91 println!(
92 "ℹ️ No scenarios found matching '{}' in `{}`.",
93 filter,
94 samples_dir.display()
95 );
96 } else {
97 println!(
98 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
99 samples_dir.display()
100 );
101 }
102 return;
103 }
104
105 println!("📊 Found {} scenario(s)\n", scenarios.len());
106
107 let mut total_parse_time = std::time::Duration::ZERO;
108 let mut total_eval_time = std::time::Duration::ZERO;
109 let mut successful_scenarios = 0;
110 let mut comparison_failures = 0;
111
112 for scenario in &scenarios {
113 println!("==============================");
114 println!("Scenario: {}", scenario.name);
115 println!(
116 "Schema: {} ({})",
117 scenario.schema_path.display(),
118 if scenario.is_msgpack {
119 "MessagePack"
120 } else {
121 "JSON"
122 }
123 );
124 println!("Data: {}\n", scenario.data_path.display());
125
126 // Clear timing data from previous scenarios
127 if show_timing {
128 json_eval_rs::enable_timing();
129 json_eval_rs::clear_timing_data();
130 }
131
132 let data_str = fs::read_to_string(&scenario.data_path)
133 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
134
135 // Step 1: Parse schema
136 let parse_start = Instant::now();
137
138 let mut eval = if scenario.is_msgpack {
139 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
140 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
141 });
142 println!(
143 " 📦 MessagePack schema size: {} bytes",
144 schema_msgpack.len()
145 );
146 JSONEval::new_from_msgpack(&schema_msgpack, None, Some(&data_str))
147 .unwrap_or_else(|e| panic!("failed to create JSONEval from MessagePack: {}", e))
148 } else {
149 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
150 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
151 });
152 JSONEval::new(&schema_str, None, Some(&data_str))
153 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
154 };
155
156 let parse_time = parse_start.elapsed();
157 println!(" 📝 Parse (new): {:?}", parse_time);
158
159 // Step 2: Evaluate
160 let eval_start = Instant::now();
161
162 eval.evaluate(&data_str, Some("{}"), None, None)
163 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
164
165 // Step 3: Validate
166 let validation_start = Instant::now();
167 let validation_result = eval
168 .validate(&data_str, None, None, None)
169 .unwrap_or_else(|e| panic!("validation failed: {}", e));
170 let validation_time = validation_start.elapsed();
171 println!(" 🛡️ Validate: {:?}", validation_time);
172
173 // Legacy behavior: get_evaluated_schema takes skip_layout: bool
174 // We pass false to ensure layout IS resolved
175 let evaluated_schema = eval.get_evaluated_schema(false);
176 let schema_value = eval.get_schema_value();
177 let eval_time = eval_start.elapsed();
178
179 println!(" ⚡ Eval: {:?}", eval_time);
180 println!(" ⏱️ Total: {:?}\n", parse_time + eval_time);
181
182 // Print detailed timing breakdown if --timing flag is set
183 if show_timing {
184 json_eval_rs::print_timing_summary();
185 }
186
187 total_parse_time += parse_time;
188 total_eval_time += eval_time;
189 successful_scenarios += 1;
190
191 // Save results
192 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
193 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
194 let value_path = samples_dir.join(format!("{}-schema-value.json", scenario.name));
195 let validation_path = samples_dir.join(format!("{}-validation.json", scenario.name));
196
197 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
198 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
199
200 let mut metadata_obj = Map::new();
201 metadata_obj.insert(
202 "dependencies".to_string(),
203 serde_json::to_value(&*eval.dependencies).unwrap(),
204 );
205 metadata_obj.insert(
206 "evaluations".to_string(),
207 serde_json::to_value(&*eval.evaluations).unwrap(),
208 );
209 metadata_obj.insert(
210 "sorted_evaluations".to_string(),
211 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
212 );
213
214 fs::write(
215 &parsed_path,
216 common::pretty_json(&Value::Object(metadata_obj)),
217 )
218 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
219
220 fs::write(&value_path, common::pretty_json(&schema_value))
221 .unwrap_or_else(|e| panic!("failed to write {}: {}", value_path.display(), e));
222
223 let validation_value = serde_json::to_value(&validation_result)
224 .unwrap_or_else(|e| panic!("failed to serialize validation result: {}", e));
225 fs::write(&validation_path, common::pretty_json(&validation_value))
226 .unwrap_or_else(|e| panic!("failed to write {}: {}", validation_path.display(), e));
227
228 println!("✅ Results saved:");
229 println!(" - {}", evaluated_path.display());
230 println!(" - {}", parsed_path.display());
231 println!(" - {}", value_path.display());
232 println!(" - {}\n", validation_path.display());
233
234 // Optional comparison
235 if enable_comparison {
236 if let Some(comp_path) = &scenario.comparison_path {
237 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
238 comparison_failures += 1;
239 }
240 println!();
241 }
242 }
243 }
244
245 // Print summary
246 println!("{}", "=".repeat(50));
247 println!("📊 Summary");
248 println!("{}", "=".repeat(50));
249 println!("Total scenarios run: {}", successful_scenarios);
250 println!("Total parse time: {:?}", total_parse_time);
251 println!("Total eval time: {:?}", total_eval_time);
252 println!("Total time: {:?}", total_parse_time + total_eval_time);
253
254 if successful_scenarios > 1 {
255 println!("\nAverage per scenario:");
256 println!(
257 " Parse: {:?}",
258 total_parse_time / successful_scenarios as u32
259 );
260 println!(
261 " Eval: {:?}",
262 total_eval_time / successful_scenarios as u32
263 );
264 }
265
266 if enable_comparison {
267 println!("Comparison failures: {}", comparison_failures);
268 }
269
270 println!("\n✅ All scenarios completed!\n");
271}44fn main() {
45 let args: Vec<String> = std::env::args().collect();
46 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("benchmark");
47
48 let mut iterations = 1usize;
49 let mut scenario_filter: Option<String> = None;
50 let mut show_cpu_info = false;
51 let mut use_parsed_schema = false;
52 let mut use_cache = false;
53 let mut concurrent_count: Option<usize> = None;
54 let mut enable_comparison = false;
55 let mut show_timing = false;
56 let mut i = 1;
57
58 // Parse arguments
59 while i < args.len() {
60 let arg = &args[i];
61
62 if arg == "-h" || arg == "--help" {
63 print_help(program_name);
64 return;
65 } else if arg == "--cpu-info" {
66 show_cpu_info = true;
67 } else if arg == "--parsed" {
68 use_parsed_schema = true;
69 } else if arg == "--cache" {
70 use_cache = true;
71 } else if arg == "--compare" {
72 enable_comparison = true;
73 } else if arg == "--timing" {
74 show_timing = true;
75 } else if arg == "--concurrent" {
76 if i + 1 >= args.len() {
77 eprintln!("Error: {} requires a value", arg);
78 print_help(program_name);
79 return;
80 }
81 i += 1;
82 match args[i].parse::<usize>() {
83 Ok(n) if n > 0 => concurrent_count = Some(n),
84 _ => {
85 eprintln!(
86 "Error: concurrent count must be a positive integer, got '{}'",
87 args[i]
88 );
89 return;
90 }
91 }
92 } else if arg == "-i" || arg == "--iterations" {
93 if i + 1 >= args.len() {
94 eprintln!("Error: {} requires a value", arg);
95 print_help(program_name);
96 return;
97 }
98 i += 1;
99 match args[i].parse::<usize>() {
100 Ok(n) if n > 0 => iterations = n,
101 _ => {
102 eprintln!(
103 "Error: iterations must be a positive integer, got '{}'",
104 args[i]
105 );
106 return;
107 }
108 }
109 } else if !arg.starts_with('-') {
110 scenario_filter = Some(arg.clone());
111 } else {
112 eprintln!("Error: unknown option '{}'", arg);
113 print_help(program_name);
114 return;
115 }
116
117 i += 1;
118 }
119
120 println!("\n🚀 JSON Evaluation - Benchmark\n");
121
122 // Show CPU info if requested or if running benchmarks
123 if show_cpu_info || iterations > 1 || concurrent_count.is_some() {
124 common::print_cpu_info();
125 }
126
127 if use_parsed_schema {
128 println!("📦 Mode: ParsedSchema (parse once, reuse for all iterations)\n");
129 }
130
131 if use_cache {
132 println!("♻️ Mode: Cache (reuse JSONEval instance across iterations)\n");
133 }
134
135 if let Some(count) = concurrent_count {
136 println!("🔀 Concurrent evaluations: {} threads\n", count);
137 } else if iterations > 1 {
138 println!("🔄 Iterations per scenario: {}\n", iterations);
139 }
140
141 if enable_comparison {
142 println!("🔍 Comparison: enabled");
143 }
144 if show_timing {
145 println!("⏱️ Internal timing: enabled");
146 }
147 if enable_comparison || show_timing {
148 println!();
149 }
150
151 let samples_dir = Path::new("samples");
152 let mut scenarios = common::discover_scenarios(samples_dir);
153
154 // Filter scenarios if a filter is provided
155 if let Some(ref filter) = scenario_filter {
156 scenarios.retain(|s| s.name.contains(filter));
157 println!("📋 Filtering scenarios matching: '{}'\n", filter);
158 }
159
160 if scenarios.is_empty() {
161 if let Some(filter) = scenario_filter {
162 println!(
163 "ℹ️ No scenarios found matching '{}' in `{}`.",
164 filter,
165 samples_dir.display()
166 );
167 } else {
168 println!(
169 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
170 samples_dir.display()
171 );
172 }
173 return;
174 }
175
176 println!("📊 Found {} scenario(s)\n", scenarios.len());
177
178 let mut total_parse_time = std::time::Duration::ZERO;
179 let mut total_eval_time = std::time::Duration::ZERO;
180 let mut successful_scenarios = 0;
181 let mut comparison_failures = 0;
182
183 for scenario in &scenarios {
184 println!("==============================");
185 println!("Scenario: {}", scenario.name);
186 println!(
187 "Schema: {} ({})",
188 scenario.schema_path.display(),
189 if scenario.is_msgpack {
190 "MessagePack"
191 } else {
192 "JSON"
193 }
194 );
195 println!("Data: {}\n", scenario.data_path.display());
196
197 // Clear timing data from previous scenarios
198 if show_timing {
199 json_eval_rs::enable_timing();
200 json_eval_rs::clear_timing_data();
201 }
202
203 let data_str = fs::read_to_string(&scenario.data_path)
204 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
205
206 println!("Running evaluation...\n");
207
208 let (parse_time, eval_time, evaluated_schema, eval, iteration_times) = if use_parsed_schema
209 {
210 // ParsedSchema mode: parse once, reuse for all iterations/threads
211 let start_time = Instant::now();
212
213 let parsed_schema = if scenario.is_msgpack {
214 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
215 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
216 });
217 println!(
218 " 📦 MessagePack schema size: {} bytes",
219 schema_msgpack.len()
220 );
221 Arc::new(
222 ParsedSchema::parse_msgpack(&schema_msgpack)
223 .unwrap_or_else(|e| panic!("failed to parse MessagePack schema: {}", e)),
224 )
225 } else {
226 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
227 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
228 });
229 Arc::new(
230 ParsedSchema::parse(&schema_str)
231 .unwrap_or_else(|e| panic!("failed to parse schema: {}", e)),
232 )
233 };
234
235 let parse_time = start_time.elapsed();
236 println!(" Schema parsing & compilation: {:?}", parse_time);
237
238 // Concurrent mode with ParsedSchema
239 if let Some(thread_count) = concurrent_count {
240 use std::thread;
241
242 let eval_start = Instant::now();
243 let mut handles = vec![];
244
245 for thread_id in 0..thread_count {
246 let parsed_clone = parsed_schema.clone();
247 let data_str_clone = data_str.clone();
248 let iter_count = iterations;
249 let thread_use_cache = use_cache;
250
251 let handle = thread::spawn(move || {
252 let mut thread_times = Vec::with_capacity(iter_count);
253 let mut last_schema = Value::Null;
254
255 let mut eval_instance = JSONEval::with_parsed_schema(
256 parsed_clone.clone(),
257 Some("{}"),
258 Some(&data_str_clone),
259 )
260 .unwrap();
261
262 for iter in 0..iter_count {
263 let iter_start = Instant::now();
264
265 if !thread_use_cache && iter > 0 {
266 eval_instance = JSONEval::with_parsed_schema(
267 parsed_clone.clone(),
268 Some("{}"),
269 Some(&data_str_clone),
270 )
271 .unwrap();
272 }
273
274 eval_instance
275 .evaluate(&data_str_clone, Some("{}"), None, None)
276 .unwrap();
277 last_schema = eval_instance.get_evaluated_schema(false);
278 thread_times.push(iter_start.elapsed());
279 }
280
281 (thread_times, last_schema, thread_id)
282 });
283 handles.push(handle);
284 }
285
286 let mut all_iteration_times = Vec::new();
287 let mut evaluated_schema = Value::Null;
288
289 for handle in handles {
290 let (thread_times, thread_schema, thread_id) = handle.join().unwrap();
291 println!(
292 " Thread {} completed {} iterations",
293 thread_id,
294 thread_times.len()
295 );
296 all_iteration_times.extend(thread_times);
297 evaluated_schema = thread_schema; // Use last thread's result
298 }
299
300 let eval_time = eval_start.elapsed();
301
302 // Create a temp eval for metadata export
303 let temp_eval = JSONEval::with_parsed_schema(
304 parsed_schema.clone(),
305 Some("{}"),
306 Some(&data_str),
307 )
308 .unwrap();
309
310 (
311 parse_time,
312 eval_time,
313 evaluated_schema,
314 temp_eval,
315 all_iteration_times,
316 )
317 } else {
318 // Sequential iterations with ParsedSchema
319 let eval_start = Instant::now();
320 let mut evaluated_schema = Value::Null;
321 let mut iteration_times = Vec::with_capacity(iterations);
322 let mut eval_instance = JSONEval::with_parsed_schema(
323 parsed_schema.clone(),
324 Some("{}"),
325 Some(&data_str),
326 )
327 .unwrap();
328
329 for iter in 0..iterations {
330 let iter_start = Instant::now();
331
332 if !use_cache && iter > 0 {
333 eval_instance = JSONEval::with_parsed_schema(
334 parsed_schema.clone(),
335 Some("{}"),
336 Some(&data_str),
337 )
338 .unwrap();
339 }
340
341 eval_instance
342 .evaluate(&data_str, Some("{}"), None, None)
343 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
344 evaluated_schema = eval_instance.get_evaluated_schema(false);
345 iteration_times.push(iter_start.elapsed());
346
347 if iterations > 1 && (iter + 1) % 10 == 0 {
348 print!(".");
349 if (iter + 1) % 50 == 0 {
350 println!(" {}/{}", iter + 1, iterations);
351 }
352 }
353 }
354
355 if iterations > 1 && iterations % 50 != 0 {
356 println!(" {}/{}", iterations, iterations);
357 }
358
359 let eval_time = eval_start.elapsed();
360 (
361 parse_time,
362 eval_time,
363 evaluated_schema,
364 eval_instance,
365 iteration_times,
366 )
367 }
368 } else {
369 // Traditional mode: parse and create JSONEval each time
370 let schema_msgpack = if scenario.is_msgpack {
371 let bytes = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
372 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
373 });
374 println!(" 📦 MessagePack schema size: {} bytes", bytes.len());
375 Some(bytes)
376 } else {
377 None
378 };
379
380 let schema_str = if !scenario.is_msgpack {
381 Some(
382 fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
383 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
384 }),
385 )
386 } else {
387 None
388 };
389
390 let start_time = Instant::now();
391 let mut eval = if scenario.is_msgpack {
392 JSONEval::new_from_msgpack(schema_msgpack.as_ref().unwrap(), None, Some(&data_str))
393 .unwrap_or_else(|e| panic!("failed to create JSONEval from MessagePack: {}", e))
394 } else {
395 JSONEval::new(schema_str.as_ref().unwrap(), None, Some(&data_str))
396 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
397 };
398 let parse_time = start_time.elapsed();
399 println!(" Schema parsing & compilation: {:?}", parse_time);
400
401 let eval_start = Instant::now();
402 let mut evaluated_schema = Value::Null;
403 let mut iteration_times = Vec::with_capacity(iterations);
404
405 for iter in 0..iterations {
406 let iter_start = Instant::now();
407
408 if !use_cache && iter > 0 {
409 eval = if scenario.is_msgpack {
410 JSONEval::new_from_msgpack(
411 schema_msgpack.as_ref().unwrap(),
412 None,
413 Some(&data_str),
414 )
415 .unwrap_or_else(|e| {
416 panic!("failed to create JSONEval from MessagePack: {}", e)
417 })
418 } else {
419 JSONEval::new(schema_str.as_ref().unwrap(), None, Some(&data_str))
420 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
421 };
422 }
423
424 eval.evaluate(&data_str, Some("{}"), None, None)
425 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
426 evaluated_schema = eval.get_evaluated_schema(false);
427 iteration_times.push(iter_start.elapsed());
428
429 if iterations > 1 && (iter + 1) % 10 == 0 {
430 print!(".");
431 if (iter + 1) % 50 == 0 {
432 println!(" {}/{}", iter + 1, iterations);
433 }
434 }
435 }
436
437 if iterations > 1 && iterations % 50 != 0 {
438 println!(" {}/{}", iterations, iterations);
439 }
440
441 let eval_time = eval_start.elapsed();
442 (
443 parse_time,
444 eval_time,
445 evaluated_schema,
446 eval,
447 iteration_times,
448 )
449 };
450
451 // Calculate statistics
452 let total_iterations = iteration_times.len();
453 if total_iterations == 1 {
454 println!(" Evaluation: {:?}", eval_time);
455 } else {
456 let avg_time = eval_time / total_iterations as u32;
457 let min_time = iteration_times.iter().min().unwrap();
458 let max_time = iteration_times.iter().max().unwrap();
459
460 println!(" Total evaluation time: {:?}", eval_time);
461 println!(" Total iterations: {}", total_iterations);
462 println!(" Average per iteration: {:?}", avg_time);
463 println!(" Min: {:?} | Max: {:?}", min_time, max_time);
464 }
465
466 let total_time = parse_time + eval_time;
467 println!("⏱️ Execution time: {:?}\n", total_time);
468
469 // Print detailed timing breakdown if --timing flag is set
470 if show_timing {
471 json_eval_rs::print_timing_summary();
472 }
473
474 // Track statistics
475 total_parse_time += parse_time;
476 total_eval_time += eval_time;
477 successful_scenarios += 1;
478
479 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
480 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
481
482 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
483 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
484
485 let mut metadata_obj = Map::new();
486 metadata_obj.insert(
487 "dependencies".to_string(),
488 serde_json::to_value(&*eval.dependencies).unwrap(),
489 );
490 metadata_obj.insert(
491 "sorted_evaluations".to_string(),
492 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
493 );
494
495 fs::write(
496 &parsed_path,
497 common::pretty_json(&Value::Object(metadata_obj)),
498 )
499 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
500
501 println!("✅ Results saved:");
502 println!(" - {}", evaluated_path.display());
503 println!(" - {}\n", parsed_path.display());
504
505 // Optional comparison
506 if enable_comparison {
507 if let Some(comp_path) = &scenario.comparison_path {
508 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
509 comparison_failures += 1;
510 }
511 println!();
512 }
513 }
514 }
515
516 // Print summary statistics
517 if successful_scenarios > 0 {
518 println!("\n{}", "=".repeat(50));
519 println!("📊 Summary Statistics");
520 println!("{}", "=".repeat(50));
521 println!("Total scenarios run: {}", successful_scenarios);
522 println!("Total parsing time: {:?}", total_parse_time);
523 println!("Total evaluation time: {:?}", total_eval_time);
524 println!("Total time: {:?}", total_parse_time + total_eval_time);
525
526 if successful_scenarios > 1 {
527 println!("\nAverage per scenario:");
528 println!(
529 " Parsing: {:?}",
530 total_parse_time / successful_scenarios as u32
531 );
532 println!(
533 " Evaluation: {:?}",
534 total_eval_time / successful_scenarios as u32
535 );
536 }
537
538 if enable_comparison {
539 println!("\nComparison failures: {}", comparison_failures);
540 }
541
542 println!("\n✅ All scenarios completed successfully!\n");
543 }
544}Sourcepub fn get_schema_value_by_path(&self, path: &str) -> Option<Value>
pub fn get_schema_value_by_path(&self, path: &str) -> Option<Value>
Get specific schema value by path, resolving any $static_array markers at or
under that path.
Sourcepub fn get_schema_value(&mut self) -> Value
pub fn get_schema_value(&mut self) -> Value
Get all schema values (data view) Mutates internal data state by overriding with values from value evaluations This corresponds to subform.get_schema_value() usage
Examples found in repository?
37fn main() {
38 let args: Vec<String> = std::env::args().collect();
39 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("basic");
40
41 let mut scenario_filter: Option<String> = None;
42 let mut enable_comparison = false;
43 let mut show_timing = false;
44 let mut i = 1;
45
46 // Parse arguments
47 while i < args.len() {
48 let arg = &args[i];
49
50 if arg == "-h" || arg == "--help" {
51 print_help(program_name);
52 return;
53 } else if arg == "--compare" {
54 enable_comparison = true;
55 } else if arg == "--timing" {
56 show_timing = true;
57 } else if !arg.starts_with('-') {
58 scenario_filter = Some(arg.clone());
59 } else {
60 eprintln!("Error: unknown option '{}'", arg);
61 print_help(program_name);
62 return;
63 }
64
65 i += 1;
66 }
67
68 println!("\n🚀 JSON Evaluation - Basic Example (JSON/MsgPack Schema)\n");
69
70 if enable_comparison {
71 println!("🔍 Comparison: enabled");
72 }
73 if show_timing {
74 println!("⏱️ Internal timing: enabled");
75 }
76 if enable_comparison || show_timing {
77 println!();
78 }
79
80 let samples_dir = Path::new("samples");
81 let mut scenarios = common::discover_scenarios(samples_dir);
82
83 // Filter scenarios if a filter is provided
84 if let Some(ref filter) = scenario_filter {
85 scenarios.retain(|s| s.name.contains(filter));
86 println!("📋 Filtering scenarios matching: '{}'\n", filter);
87 }
88
89 if scenarios.is_empty() {
90 if let Some(filter) = scenario_filter {
91 println!(
92 "ℹ️ No scenarios found matching '{}' in `{}`.",
93 filter,
94 samples_dir.display()
95 );
96 } else {
97 println!(
98 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
99 samples_dir.display()
100 );
101 }
102 return;
103 }
104
105 println!("📊 Found {} scenario(s)\n", scenarios.len());
106
107 let mut total_parse_time = std::time::Duration::ZERO;
108 let mut total_eval_time = std::time::Duration::ZERO;
109 let mut successful_scenarios = 0;
110 let mut comparison_failures = 0;
111
112 for scenario in &scenarios {
113 println!("==============================");
114 println!("Scenario: {}", scenario.name);
115 println!(
116 "Schema: {} ({})",
117 scenario.schema_path.display(),
118 if scenario.is_msgpack {
119 "MessagePack"
120 } else {
121 "JSON"
122 }
123 );
124 println!("Data: {}\n", scenario.data_path.display());
125
126 // Clear timing data from previous scenarios
127 if show_timing {
128 json_eval_rs::enable_timing();
129 json_eval_rs::clear_timing_data();
130 }
131
132 let data_str = fs::read_to_string(&scenario.data_path)
133 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
134
135 // Step 1: Parse schema
136 let parse_start = Instant::now();
137
138 let mut eval = if scenario.is_msgpack {
139 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
140 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
141 });
142 println!(
143 " 📦 MessagePack schema size: {} bytes",
144 schema_msgpack.len()
145 );
146 JSONEval::new_from_msgpack(&schema_msgpack, None, Some(&data_str))
147 .unwrap_or_else(|e| panic!("failed to create JSONEval from MessagePack: {}", e))
148 } else {
149 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
150 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
151 });
152 JSONEval::new(&schema_str, None, Some(&data_str))
153 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
154 };
155
156 let parse_time = parse_start.elapsed();
157 println!(" 📝 Parse (new): {:?}", parse_time);
158
159 // Step 2: Evaluate
160 let eval_start = Instant::now();
161
162 eval.evaluate(&data_str, Some("{}"), None, None)
163 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
164
165 // Step 3: Validate
166 let validation_start = Instant::now();
167 let validation_result = eval
168 .validate(&data_str, None, None, None)
169 .unwrap_or_else(|e| panic!("validation failed: {}", e));
170 let validation_time = validation_start.elapsed();
171 println!(" 🛡️ Validate: {:?}", validation_time);
172
173 // Legacy behavior: get_evaluated_schema takes skip_layout: bool
174 // We pass false to ensure layout IS resolved
175 let evaluated_schema = eval.get_evaluated_schema(false);
176 let schema_value = eval.get_schema_value();
177 let eval_time = eval_start.elapsed();
178
179 println!(" ⚡ Eval: {:?}", eval_time);
180 println!(" ⏱️ Total: {:?}\n", parse_time + eval_time);
181
182 // Print detailed timing breakdown if --timing flag is set
183 if show_timing {
184 json_eval_rs::print_timing_summary();
185 }
186
187 total_parse_time += parse_time;
188 total_eval_time += eval_time;
189 successful_scenarios += 1;
190
191 // Save results
192 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
193 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
194 let value_path = samples_dir.join(format!("{}-schema-value.json", scenario.name));
195 let validation_path = samples_dir.join(format!("{}-validation.json", scenario.name));
196
197 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
198 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
199
200 let mut metadata_obj = Map::new();
201 metadata_obj.insert(
202 "dependencies".to_string(),
203 serde_json::to_value(&*eval.dependencies).unwrap(),
204 );
205 metadata_obj.insert(
206 "evaluations".to_string(),
207 serde_json::to_value(&*eval.evaluations).unwrap(),
208 );
209 metadata_obj.insert(
210 "sorted_evaluations".to_string(),
211 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
212 );
213
214 fs::write(
215 &parsed_path,
216 common::pretty_json(&Value::Object(metadata_obj)),
217 )
218 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
219
220 fs::write(&value_path, common::pretty_json(&schema_value))
221 .unwrap_or_else(|e| panic!("failed to write {}: {}", value_path.display(), e));
222
223 let validation_value = serde_json::to_value(&validation_result)
224 .unwrap_or_else(|e| panic!("failed to serialize validation result: {}", e));
225 fs::write(&validation_path, common::pretty_json(&validation_value))
226 .unwrap_or_else(|e| panic!("failed to write {}: {}", validation_path.display(), e));
227
228 println!("✅ Results saved:");
229 println!(" - {}", evaluated_path.display());
230 println!(" - {}", parsed_path.display());
231 println!(" - {}", value_path.display());
232 println!(" - {}\n", validation_path.display());
233
234 // Optional comparison
235 if enable_comparison {
236 if let Some(comp_path) = &scenario.comparison_path {
237 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
238 comparison_failures += 1;
239 }
240 println!();
241 }
242 }
243 }
244
245 // Print summary
246 println!("{}", "=".repeat(50));
247 println!("📊 Summary");
248 println!("{}", "=".repeat(50));
249 println!("Total scenarios run: {}", successful_scenarios);
250 println!("Total parse time: {:?}", total_parse_time);
251 println!("Total eval time: {:?}", total_eval_time);
252 println!("Total time: {:?}", total_parse_time + total_eval_time);
253
254 if successful_scenarios > 1 {
255 println!("\nAverage per scenario:");
256 println!(
257 " Parse: {:?}",
258 total_parse_time / successful_scenarios as u32
259 );
260 println!(
261 " Eval: {:?}",
262 total_eval_time / successful_scenarios as u32
263 );
264 }
265
266 if enable_comparison {
267 println!("Comparison failures: {}", comparison_failures);
268 }
269
270 println!("\n✅ All scenarios completed!\n");
271}Sourcepub fn get_schema_value_array(&self) -> Value
pub fn get_schema_value_array(&self) -> Value
Get all schema values as array of path-value pairs Returns [{path: “”, value: “”}, …]
§Returns
Array of objects containing path (dotted notation) and value pairs from value evaluations
Sourcepub fn get_schema_value_object(&self) -> Value
pub fn get_schema_value_object(&self) -> Value
Get all schema values as object with dotted path keys Returns {path: value, …}
§Returns
Flat object with dotted notation paths as keys and evaluated values
Sourcepub fn get_evaluated_schema_without_params(
&mut self,
skip_layout: bool,
) -> Value
pub fn get_evaluated_schema_without_params( &mut self, skip_layout: bool, ) -> Value
Get evaluated schema without $params
Sourcepub fn get_evaluated_schema_msgpack(
&mut self,
skip_layout: bool,
) -> Result<Vec<u8>, String>
pub fn get_evaluated_schema_msgpack( &mut self, skip_layout: bool, ) -> Result<Vec<u8>, String>
Get evaluated schema as MessagePack bytes
Sourcepub fn get_evaluated_schema_by_path(
&mut self,
path: &str,
skip_layout: bool,
) -> Option<Value>
pub fn get_evaluated_schema_by_path( &mut self, path: &str, skip_layout: bool, ) -> Option<Value>
Get value from evaluated schema by path
Sourcepub fn get_evaluated_schema_by_paths(
&mut self,
paths: &[String],
skip_layout: bool,
format: Option<ReturnFormat>,
) -> Value
pub fn get_evaluated_schema_by_paths( &mut self, paths: &[String], skip_layout: bool, format: Option<ReturnFormat>, ) -> Value
Get evaluated schema parts by multiple paths
Sourcepub fn get_schema_by_path(&self, path: &str) -> Option<Value>
pub fn get_schema_by_path(&self, path: &str) -> Option<Value>
Get original (unevaluated) schema by path
Sourcepub fn get_schema_by_paths(
&self,
paths: &[String],
format: Option<ReturnFormat>,
) -> Value
pub fn get_schema_by_paths( &self, paths: &[String], format: Option<ReturnFormat>, ) -> Value
Get original schema by multiple paths
Sourcepub fn flatten_object(
prefix: &str,
value: &Value,
result: &mut Map<String, Value>,
)
pub fn flatten_object( prefix: &str, value: &Value, result: &mut Map<String, Value>, )
Flatten a nested object key-value pair to dotted keys
pub fn convert_to_format(value: Value, format: ReturnFormat) -> Value
Source§impl JSONEval
impl JSONEval
Sourcepub fn run_logic(
&mut self,
logic_id: CompiledLogicId,
data: Option<&Value>,
context: Option<&Value>,
) -> Result<Value, String>
pub fn run_logic( &mut self, logic_id: CompiledLogicId, data: Option<&Value>, context: Option<&Value>, ) -> Result<Value, String>
Run pre-compiled logic against current data
Sourcepub fn compile_logic(&self, logic_str: &str) -> Result<CompiledLogicId, String>
pub fn compile_logic(&self, logic_str: &str) -> Result<CompiledLogicId, String>
Compile a logic expression from a JSON string and store it globally
Sourcepub fn compile_logic_value(
&self,
logic: &Value,
) -> Result<CompiledLogicId, String>
pub fn compile_logic_value( &self, logic: &Value, ) -> Result<CompiledLogicId, String>
Compile a logic expression from a Value and store it globally
Source§impl JSONEval
impl JSONEval
Sourcepub fn evaluate_subform(
&mut self,
subform_path: &str,
data: &str,
context: Option<&str>,
paths: Option<&[String]>,
token: Option<&CancellationToken>,
) -> Result<(), String>
pub fn evaluate_subform( &mut self, subform_path: &str, data: &str, context: Option<&str>, paths: Option<&[String]>, token: Option<&CancellationToken>, ) -> Result<(), String>
Evaluate a subform identified by subform_path.
The path may include a trailing item index to bind the evaluation to a specific array element and enable the two-tier cache-swap strategy automatically:
// Evaluate riders item 1 with index-aware cache
eval.evaluate_subform("illustration.product_benefit.riders.1", data, ctx, None, None)?;Without a trailing index, the subform is evaluated in isolation (no cache swap).
Sourcepub fn validate_subform(
&mut self,
subform_path: &str,
data: &str,
context: Option<&str>,
paths: Option<&[String]>,
token: Option<&CancellationToken>,
) -> Result<ValidationResult, String>
pub fn validate_subform( &mut self, subform_path: &str, data: &str, context: Option<&str>, paths: Option<&[String]>, token: Option<&CancellationToken>, ) -> Result<ValidationResult, String>
Validate subform data against its schema rules.
Supports the same trailing-index path syntax as evaluate_subform. When an index
is present the parent cache is swapped in first, ensuring rule evaluations that
depend on $params tables share already-computed parent-form results.
Sourcepub fn evaluate_dependents_subform(
&mut self,
subform_path: &str,
changed_paths: &[String],
data: Option<&str>,
context: Option<&str>,
re_evaluate: bool,
token: Option<&CancellationToken>,
canceled_paths: Option<&mut Vec<String>>,
include_subforms: bool,
) -> Result<Value, String>
pub fn evaluate_dependents_subform( &mut self, subform_path: &str, changed_paths: &[String], data: Option<&str>, context: Option<&str>, re_evaluate: bool, token: Option<&CancellationToken>, canceled_paths: Option<&mut Vec<String>>, include_subforms: bool, ) -> Result<Value, String>
Evaluate dependents in a subform when a field changes.
Supports the same trailing-index path syntax as evaluate_subform. When an index
is present the parent cache is swapped in, so dependent evaluation runs with
Tier-2 entries visible and item-scoped version bumps propagate to eval_generation.
Sourcepub fn resolve_layout_subform(
&mut self,
subform_path: &str,
evaluate: bool,
) -> Result<(), String>
pub fn resolve_layout_subform( &mut self, subform_path: &str, evaluate: bool, ) -> Result<(), String>
Resolve layout for subform.
Sourcepub fn get_evaluated_schema_subform(
&mut self,
subform_path: &str,
resolve_layout: bool,
) -> Value
pub fn get_evaluated_schema_subform( &mut self, subform_path: &str, resolve_layout: bool, ) -> Value
Get evaluated schema from subform.
Sourcepub fn get_schema_value_subform(&mut self, subform_path: &str) -> Value
pub fn get_schema_value_subform(&mut self, subform_path: &str) -> Value
Get schema value from subform in nested object format (all .value fields).
Sourcepub fn get_schema_value_array_subform(&self, subform_path: &str) -> Value
pub fn get_schema_value_array_subform(&self, subform_path: &str) -> Value
Get schema values from subform as a flat array of path-value pairs.
Sourcepub fn get_schema_value_object_subform(&self, subform_path: &str) -> Value
pub fn get_schema_value_object_subform(&self, subform_path: &str) -> Value
Get schema values from subform as a flat object with dotted path keys.
Sourcepub fn get_evaluated_schema_without_params_subform(
&mut self,
subform_path: &str,
resolve_layout: bool,
) -> Value
pub fn get_evaluated_schema_without_params_subform( &mut self, subform_path: &str, resolve_layout: bool, ) -> Value
Get evaluated schema without $params from subform.
Sourcepub fn get_evaluated_schema_by_path_subform(
&mut self,
subform_path: &str,
schema_path: &str,
skip_layout: bool,
) -> Option<Value>
pub fn get_evaluated_schema_by_path_subform( &mut self, subform_path: &str, schema_path: &str, skip_layout: bool, ) -> Option<Value>
Get evaluated schema by specific path from subform.
Sourcepub fn get_evaluated_schema_by_paths_subform(
&mut self,
subform_path: &str,
schema_paths: &[String],
skip_layout: bool,
format: Option<ReturnFormat>,
) -> Value
pub fn get_evaluated_schema_by_paths_subform( &mut self, subform_path: &str, schema_paths: &[String], skip_layout: bool, format: Option<ReturnFormat>, ) -> Value
Get evaluated schema by multiple paths from subform.
Sourcepub fn get_schema_by_path_subform(
&self,
subform_path: &str,
schema_path: &str,
) -> Option<Value>
pub fn get_schema_by_path_subform( &self, subform_path: &str, schema_path: &str, ) -> Option<Value>
Get schema by specific path from subform.
Sourcepub fn get_schema_by_paths_subform(
&self,
subform_path: &str,
schema_paths: &[String],
format: Option<ReturnFormat>,
) -> Value
pub fn get_schema_by_paths_subform( &self, subform_path: &str, schema_paths: &[String], format: Option<ReturnFormat>, ) -> Value
Get schema by multiple paths from subform.
Sourcepub fn get_subform_paths(&self) -> Vec<String>
pub fn get_subform_paths(&self) -> Vec<String>
Get list of available subform paths.
Sourcepub fn has_subform(&self, subform_path: &str) -> bool
pub fn has_subform(&self, subform_path: &str) -> bool
Check if a subform exists at the given path.
Source§impl JSONEval
impl JSONEval
Sourcepub fn validate(
&mut self,
data: &str,
context: Option<&str>,
paths: Option<&[String]>,
token: Option<&CancellationToken>,
) -> Result<ValidationResult, String>
pub fn validate( &mut self, data: &str, context: Option<&str>, paths: Option<&[String]>, token: Option<&CancellationToken>, ) -> Result<ValidationResult, String>
Validate data against schema rules
Examples found in repository?
37fn main() {
38 let args: Vec<String> = std::env::args().collect();
39 let program_name = args.get(0).map(|s| s.as_str()).unwrap_or("basic");
40
41 let mut scenario_filter: Option<String> = None;
42 let mut enable_comparison = false;
43 let mut show_timing = false;
44 let mut i = 1;
45
46 // Parse arguments
47 while i < args.len() {
48 let arg = &args[i];
49
50 if arg == "-h" || arg == "--help" {
51 print_help(program_name);
52 return;
53 } else if arg == "--compare" {
54 enable_comparison = true;
55 } else if arg == "--timing" {
56 show_timing = true;
57 } else if !arg.starts_with('-') {
58 scenario_filter = Some(arg.clone());
59 } else {
60 eprintln!("Error: unknown option '{}'", arg);
61 print_help(program_name);
62 return;
63 }
64
65 i += 1;
66 }
67
68 println!("\n🚀 JSON Evaluation - Basic Example (JSON/MsgPack Schema)\n");
69
70 if enable_comparison {
71 println!("🔍 Comparison: enabled");
72 }
73 if show_timing {
74 println!("⏱️ Internal timing: enabled");
75 }
76 if enable_comparison || show_timing {
77 println!();
78 }
79
80 let samples_dir = Path::new("samples");
81 let mut scenarios = common::discover_scenarios(samples_dir);
82
83 // Filter scenarios if a filter is provided
84 if let Some(ref filter) = scenario_filter {
85 scenarios.retain(|s| s.name.contains(filter));
86 println!("📋 Filtering scenarios matching: '{}'\n", filter);
87 }
88
89 if scenarios.is_empty() {
90 if let Some(filter) = scenario_filter {
91 println!(
92 "ℹ️ No scenarios found matching '{}' in `{}`.",
93 filter,
94 samples_dir.display()
95 );
96 } else {
97 println!(
98 "ℹ️ No scenarios discovered in `{}`. Add files like `name.json` and `name-data.json`.",
99 samples_dir.display()
100 );
101 }
102 return;
103 }
104
105 println!("📊 Found {} scenario(s)\n", scenarios.len());
106
107 let mut total_parse_time = std::time::Duration::ZERO;
108 let mut total_eval_time = std::time::Duration::ZERO;
109 let mut successful_scenarios = 0;
110 let mut comparison_failures = 0;
111
112 for scenario in &scenarios {
113 println!("==============================");
114 println!("Scenario: {}", scenario.name);
115 println!(
116 "Schema: {} ({})",
117 scenario.schema_path.display(),
118 if scenario.is_msgpack {
119 "MessagePack"
120 } else {
121 "JSON"
122 }
123 );
124 println!("Data: {}\n", scenario.data_path.display());
125
126 // Clear timing data from previous scenarios
127 if show_timing {
128 json_eval_rs::enable_timing();
129 json_eval_rs::clear_timing_data();
130 }
131
132 let data_str = fs::read_to_string(&scenario.data_path)
133 .unwrap_or_else(|e| panic!("failed to read {}: {}", scenario.data_path.display(), e));
134
135 // Step 1: Parse schema
136 let parse_start = Instant::now();
137
138 let mut eval = if scenario.is_msgpack {
139 let schema_msgpack = fs::read(&scenario.schema_path).unwrap_or_else(|e| {
140 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
141 });
142 println!(
143 " 📦 MessagePack schema size: {} bytes",
144 schema_msgpack.len()
145 );
146 JSONEval::new_from_msgpack(&schema_msgpack, None, Some(&data_str))
147 .unwrap_or_else(|e| panic!("failed to create JSONEval from MessagePack: {}", e))
148 } else {
149 let schema_str = fs::read_to_string(&scenario.schema_path).unwrap_or_else(|e| {
150 panic!("failed to read {}: {}", scenario.schema_path.display(), e)
151 });
152 JSONEval::new(&schema_str, None, Some(&data_str))
153 .unwrap_or_else(|e| panic!("failed to create JSONEval: {}", e))
154 };
155
156 let parse_time = parse_start.elapsed();
157 println!(" 📝 Parse (new): {:?}", parse_time);
158
159 // Step 2: Evaluate
160 let eval_start = Instant::now();
161
162 eval.evaluate(&data_str, Some("{}"), None, None)
163 .unwrap_or_else(|e| panic!("evaluation failed: {}", e));
164
165 // Step 3: Validate
166 let validation_start = Instant::now();
167 let validation_result = eval
168 .validate(&data_str, None, None, None)
169 .unwrap_or_else(|e| panic!("validation failed: {}", e));
170 let validation_time = validation_start.elapsed();
171 println!(" 🛡️ Validate: {:?}", validation_time);
172
173 // Legacy behavior: get_evaluated_schema takes skip_layout: bool
174 // We pass false to ensure layout IS resolved
175 let evaluated_schema = eval.get_evaluated_schema(false);
176 let schema_value = eval.get_schema_value();
177 let eval_time = eval_start.elapsed();
178
179 println!(" ⚡ Eval: {:?}", eval_time);
180 println!(" ⏱️ Total: {:?}\n", parse_time + eval_time);
181
182 // Print detailed timing breakdown if --timing flag is set
183 if show_timing {
184 json_eval_rs::print_timing_summary();
185 }
186
187 total_parse_time += parse_time;
188 total_eval_time += eval_time;
189 successful_scenarios += 1;
190
191 // Save results
192 let evaluated_path = samples_dir.join(format!("{}-evaluated-schema.json", scenario.name));
193 let parsed_path = samples_dir.join(format!("{}-parsed-schema.json", scenario.name));
194 let value_path = samples_dir.join(format!("{}-schema-value.json", scenario.name));
195 let validation_path = samples_dir.join(format!("{}-validation.json", scenario.name));
196
197 fs::write(&evaluated_path, common::pretty_json(&evaluated_schema))
198 .unwrap_or_else(|e| panic!("failed to write {}: {}", evaluated_path.display(), e));
199
200 let mut metadata_obj = Map::new();
201 metadata_obj.insert(
202 "dependencies".to_string(),
203 serde_json::to_value(&*eval.dependencies).unwrap(),
204 );
205 metadata_obj.insert(
206 "evaluations".to_string(),
207 serde_json::to_value(&*eval.evaluations).unwrap(),
208 );
209 metadata_obj.insert(
210 "sorted_evaluations".to_string(),
211 serde_json::to_value(&*eval.sorted_evaluations).unwrap(),
212 );
213
214 fs::write(
215 &parsed_path,
216 common::pretty_json(&Value::Object(metadata_obj)),
217 )
218 .unwrap_or_else(|e| panic!("failed to write {}: {}", parsed_path.display(), e));
219
220 fs::write(&value_path, common::pretty_json(&schema_value))
221 .unwrap_or_else(|e| panic!("failed to write {}: {}", value_path.display(), e));
222
223 let validation_value = serde_json::to_value(&validation_result)
224 .unwrap_or_else(|e| panic!("failed to serialize validation result: {}", e));
225 fs::write(&validation_path, common::pretty_json(&validation_value))
226 .unwrap_or_else(|e| panic!("failed to write {}: {}", validation_path.display(), e));
227
228 println!("✅ Results saved:");
229 println!(" - {}", evaluated_path.display());
230 println!(" - {}", parsed_path.display());
231 println!(" - {}", value_path.display());
232 println!(" - {}\n", validation_path.display());
233
234 // Optional comparison
235 if enable_comparison {
236 if let Some(comp_path) = &scenario.comparison_path {
237 if common::compare_with_expected(&evaluated_schema, comp_path).is_err() {
238 comparison_failures += 1;
239 }
240 println!();
241 }
242 }
243 }
244
245 // Print summary
246 println!("{}", "=".repeat(50));
247 println!("📊 Summary");
248 println!("{}", "=".repeat(50));
249 println!("Total scenarios run: {}", successful_scenarios);
250 println!("Total parse time: {:?}", total_parse_time);
251 println!("Total eval time: {:?}", total_eval_time);
252 println!("Total time: {:?}", total_parse_time + total_eval_time);
253
254 if successful_scenarios > 1 {
255 println!("\nAverage per scenario:");
256 println!(
257 " Parse: {:?}",
258 total_parse_time / successful_scenarios as u32
259 );
260 println!(
261 " Eval: {:?}",
262 total_eval_time / successful_scenarios as u32
263 );
264 }
265
266 if enable_comparison {
267 println!("Comparison failures: {}", comparison_failures);
268 }
269
270 println!("\n✅ All scenarios completed!\n");
271}