1#[cfg(windows)]
11#[global_allocator]
12static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
13
14pub mod rlogic;
15pub mod table_evaluate;
16pub mod table_metadata;
17pub mod topo_sort;
18pub mod parse_schema;
19
20pub mod parsed_schema;
21pub mod parsed_schema_cache;
22pub mod json_parser;
23pub mod path_utils;
24pub mod eval_data;
25pub mod eval_cache;
26pub mod subform_methods;
27
28#[cfg(feature = "ffi")]
30pub mod ffi;
31
32#[cfg(feature = "wasm")]
34pub mod wasm;
35
36use indexmap::{IndexMap, IndexSet};
38pub use rlogic::{
39 CompiledLogic, CompiledLogicStore, Evaluator,
40 LogicId, RLogic, RLogicConfig,
41 CompiledLogicId, CompiledLogicStoreStats,
42};
43use serde::{Deserialize, Serialize};
44pub use table_metadata::TableMetadata;
45pub use path_utils::ArrayMetadata;
46pub use eval_data::EvalData;
47pub use eval_cache::{EvalCache, CacheKey, CacheStats};
48pub use parsed_schema::ParsedSchema;
49pub use parsed_schema_cache::{ParsedSchemaCache, ParsedSchemaCacheStats, PARSED_SCHEMA_CACHE};
50use serde::de::Error as _;
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
54pub enum ReturnFormat {
55 #[default]
58 Nested,
59 Flat,
62 Array,
65}
66use serde_json::{Value};
67
68#[cfg(feature = "parallel")]
69use rayon::prelude::*;
70
71use std::mem;
72use std::sync::{Arc, Mutex};
73use std::time::Instant;
74use std::cell::RefCell;
75
76thread_local! {
78 static TIMING_ENABLED: RefCell<bool> = RefCell::new(std::env::var("JSONEVAL_TIMING").is_ok());
79 static TIMING_DATA: RefCell<Vec<(String, std::time::Duration)>> = RefCell::new(Vec::new());
80}
81
82#[inline]
84fn is_timing_enabled() -> bool {
85 TIMING_ENABLED.with(|enabled| *enabled.borrow())
86}
87
88pub fn enable_timing() {
90 TIMING_ENABLED.with(|enabled| {
91 *enabled.borrow_mut() = true;
92 });
93}
94
95pub fn disable_timing() {
97 TIMING_ENABLED.with(|enabled| {
98 *enabled.borrow_mut() = false;
99 });
100}
101
102#[inline]
104fn record_timing(label: &str, duration: std::time::Duration) {
105 if is_timing_enabled() {
106 TIMING_DATA.with(|data| {
107 data.borrow_mut().push((label.to_string(), duration));
108 });
109 }
110}
111
112pub fn print_timing_summary() {
114 if !is_timing_enabled() {
115 return;
116 }
117
118 TIMING_DATA.with(|data| {
119 let timings = data.borrow();
120 if timings.is_empty() {
121 return;
122 }
123
124 eprintln!("\nš Timing Summary (JSONEVAL_TIMING enabled)");
125 eprintln!("{}", "=".repeat(60));
126
127 let mut total = std::time::Duration::ZERO;
128 for (label, duration) in timings.iter() {
129 eprintln!("{:40} {:>12?}", label, duration);
130 total += *duration;
131 }
132
133 eprintln!("{}", "=".repeat(60));
134 eprintln!("{:40} {:>12?}", "TOTAL", total);
135 eprintln!();
136 });
137}
138
139pub fn clear_timing_data() {
141 TIMING_DATA.with(|data| {
142 data.borrow_mut().clear();
143 });
144}
145
146macro_rules! time_block {
148 ($label:expr, $block:block) => {{
149 let _start = if is_timing_enabled() {
150 Some(Instant::now())
151 } else {
152 None
153 };
154 let result = $block;
155 if let Some(start) = _start {
156 record_timing($label, start.elapsed());
157 }
158 result
159 }};
160}
161
162pub fn version() -> &'static str {
164 env!("CARGO_PKG_VERSION")
165}
166
167fn clean_float_noise(value: Value) -> Value {
170 const EPSILON: f64 = 1e-10;
171
172 match value {
173 Value::Number(n) => {
174 if let Some(f) = n.as_f64() {
175 if f.abs() < EPSILON {
176 Value::Number(serde_json::Number::from(0))
178 } else if f.fract().abs() < EPSILON {
179 Value::Number(serde_json::Number::from(f.round() as i64))
181 } else {
182 Value::Number(n)
183 }
184 } else {
185 Value::Number(n)
186 }
187 }
188 Value::Array(arr) => {
189 Value::Array(arr.into_iter().map(clean_float_noise).collect())
190 }
191 Value::Object(obj) => {
192 Value::Object(obj.into_iter().map(|(k, v)| (k, clean_float_noise(v))).collect())
193 }
194 _ => value,
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct DependentItem {
201 pub ref_path: String,
202 pub clear: Option<Value>, pub value: Option<Value>, }
205
206pub struct JSONEval {
207 pub schema: Arc<Value>,
208 pub engine: Arc<RLogic>,
209 pub evaluations: Arc<IndexMap<String, LogicId>>,
211 pub tables: Arc<IndexMap<String, Value>>,
212 pub table_metadata: Arc<IndexMap<String, TableMetadata>>,
214 pub dependencies: Arc<IndexMap<String, IndexSet<String>>>,
215 pub sorted_evaluations: Arc<Vec<Vec<String>>>,
218 pub dependents_evaluations: Arc<IndexMap<String, Vec<DependentItem>>>,
221 pub rules_evaluations: Arc<Vec<String>>,
223 pub fields_with_rules: Arc<Vec<String>>,
225 pub others_evaluations: Arc<Vec<String>>,
227 pub value_evaluations: Arc<Vec<String>>,
229 pub layout_paths: Arc<Vec<String>>,
231 pub options_templates: Arc<Vec<(String, String, String)>>,
233 pub subforms: IndexMap<String, Box<JSONEval>>,
236 pub context: Value,
237 pub data: Value,
238 pub evaluated_schema: Value,
239 pub eval_data: EvalData,
240 pub eval_cache: EvalCache,
242 pub cache_enabled: bool,
245 eval_lock: Mutex<()>,
247 cached_msgpack_schema: Option<Vec<u8>>,
250}
251
252impl Clone for JSONEval {
253 fn clone(&self) -> Self {
254 Self {
255 cache_enabled: self.cache_enabled,
256 schema: Arc::clone(&self.schema),
257 engine: Arc::clone(&self.engine),
258 evaluations: self.evaluations.clone(),
259 tables: self.tables.clone(),
260 table_metadata: self.table_metadata.clone(),
261 dependencies: self.dependencies.clone(),
262 sorted_evaluations: self.sorted_evaluations.clone(),
263 dependents_evaluations: self.dependents_evaluations.clone(),
264 rules_evaluations: self.rules_evaluations.clone(),
265 fields_with_rules: self.fields_with_rules.clone(),
266 others_evaluations: self.others_evaluations.clone(),
267 value_evaluations: self.value_evaluations.clone(),
268 layout_paths: self.layout_paths.clone(),
269 options_templates: self.options_templates.clone(),
270 subforms: self.subforms.clone(),
271 context: self.context.clone(),
272 data: self.data.clone(),
273 evaluated_schema: self.evaluated_schema.clone(),
274 eval_data: self.eval_data.clone(),
275 eval_cache: EvalCache::new(), eval_lock: Mutex::new(()), cached_msgpack_schema: self.cached_msgpack_schema.clone(),
278 }
279 }
280}
281
282impl JSONEval {
283 pub fn new(
284 schema: &str,
285 context: Option<&str>,
286 data: Option<&str>,
287 ) -> Result<Self, serde_json::Error> {
288 time_block!("JSONEval::new() [total]", {
289 let schema_val: Value = time_block!(" parse schema JSON", {
291 serde_json::from_str(schema)?
292 });
293 let context: Value = time_block!(" parse context JSON", {
294 json_parser::parse_json_str(context.unwrap_or("{}")).map_err(serde_json::Error::custom)?
295 });
296 let data: Value = time_block!(" parse data JSON", {
297 json_parser::parse_json_str(data.unwrap_or("{}")).map_err(serde_json::Error::custom)?
298 });
299 let evaluated_schema = schema_val.clone();
300 let engine_config = RLogicConfig::default();
302
303 let mut instance = time_block!(" create instance struct", {
304 Self {
305 schema: Arc::new(schema_val),
306 evaluations: Arc::new(IndexMap::new()),
307 tables: Arc::new(IndexMap::new()),
308 table_metadata: Arc::new(IndexMap::new()),
309 dependencies: Arc::new(IndexMap::new()),
310 sorted_evaluations: Arc::new(Vec::new()),
311 dependents_evaluations: Arc::new(IndexMap::new()),
312 rules_evaluations: Arc::new(Vec::new()),
313 fields_with_rules: Arc::new(Vec::new()),
314 others_evaluations: Arc::new(Vec::new()),
315 value_evaluations: Arc::new(Vec::new()),
316 layout_paths: Arc::new(Vec::new()),
317 options_templates: Arc::new(Vec::new()),
318 subforms: IndexMap::new(),
319 engine: Arc::new(RLogic::with_config(engine_config)),
320 context: context.clone(),
321 data: data.clone(),
322 evaluated_schema: evaluated_schema.clone(),
323 eval_data: EvalData::with_schema_data_context(&evaluated_schema, &data, &context),
324 eval_cache: EvalCache::new(),
325 cache_enabled: true, eval_lock: Mutex::new(()),
327 cached_msgpack_schema: None, }
329 });
330 time_block!(" parse_schema", {
331 parse_schema::legacy::parse_schema(&mut instance).map_err(serde_json::Error::custom)?
332 });
333 Ok(instance)
334 })
335 }
336
337 pub fn new_from_msgpack(
349 schema_msgpack: &[u8],
350 context: Option<&str>,
351 data: Option<&str>,
352 ) -> Result<Self, String> {
353 let cached_msgpack = schema_msgpack.to_vec();
355
356 let schema_val: Value = rmp_serde::from_slice(schema_msgpack)
358 .map_err(|e| format!("Failed to deserialize MessagePack schema: {}", e))?;
359
360 let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))
361 .map_err(|e| format!("Failed to parse context: {}", e))?;
362 let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))
363 .map_err(|e| format!("Failed to parse data: {}", e))?;
364 let evaluated_schema = schema_val.clone();
365 let engine_config = RLogicConfig::default();
366
367 let mut instance = Self {
368 schema: Arc::new(schema_val),
369 evaluations: Arc::new(IndexMap::new()),
370 tables: Arc::new(IndexMap::new()),
371 table_metadata: Arc::new(IndexMap::new()),
372 dependencies: Arc::new(IndexMap::new()),
373 sorted_evaluations: Arc::new(Vec::new()),
374 dependents_evaluations: Arc::new(IndexMap::new()),
375 rules_evaluations: Arc::new(Vec::new()),
376 fields_with_rules: Arc::new(Vec::new()),
377 others_evaluations: Arc::new(Vec::new()),
378 value_evaluations: Arc::new(Vec::new()),
379 layout_paths: Arc::new(Vec::new()),
380 options_templates: Arc::new(Vec::new()),
381 subforms: IndexMap::new(),
382 engine: Arc::new(RLogic::with_config(engine_config)),
383 context: context.clone(),
384 data: data.clone(),
385 evaluated_schema: evaluated_schema.clone(),
386 eval_data: EvalData::with_schema_data_context(&evaluated_schema, &data, &context),
387 eval_cache: EvalCache::new(),
388 cache_enabled: true, eval_lock: Mutex::new(()),
390 cached_msgpack_schema: Some(cached_msgpack), };
392 parse_schema::legacy::parse_schema(&mut instance)?;
393 Ok(instance)
394 }
395
396 pub fn with_parsed_schema(
424 parsed: Arc<ParsedSchema>,
425 context: Option<&str>,
426 data: Option<&str>,
427 ) -> Result<Self, String> {
428 let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))
429 .map_err(|e| format!("Failed to parse context: {}", e))?;
430 let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))
431 .map_err(|e| format!("Failed to parse data: {}", e))?;
432
433 let evaluated_schema = parsed.schema.clone();
434
435 let engine = parsed.engine.clone();
438
439 let mut subforms = IndexMap::new();
442 for (path, subform_parsed) in &parsed.subforms {
443 let subform_eval = JSONEval::with_parsed_schema(
445 subform_parsed.clone(),
446 Some("{}"),
447 None
448 )?;
449 subforms.insert(path.clone(), Box::new(subform_eval));
450 }
451
452 let instance = Self {
453 schema: Arc::clone(&parsed.schema),
454 evaluations: Arc::clone(&parsed.evaluations),
456 tables: Arc::clone(&parsed.tables),
457 table_metadata: Arc::clone(&parsed.table_metadata),
458 dependencies: Arc::clone(&parsed.dependencies),
459 sorted_evaluations: Arc::clone(&parsed.sorted_evaluations),
460 dependents_evaluations: Arc::clone(&parsed.dependents_evaluations),
461 rules_evaluations: Arc::clone(&parsed.rules_evaluations),
462 fields_with_rules: Arc::clone(&parsed.fields_with_rules),
463 others_evaluations: Arc::clone(&parsed.others_evaluations),
464 value_evaluations: Arc::clone(&parsed.value_evaluations),
465 layout_paths: Arc::clone(&parsed.layout_paths),
466 options_templates: Arc::clone(&parsed.options_templates),
467 subforms,
468 engine,
469 context: context.clone(),
470 data: data.clone(),
471 evaluated_schema: (*evaluated_schema).clone(),
472 eval_data: EvalData::with_schema_data_context(&evaluated_schema, &data, &context),
473 eval_cache: EvalCache::new(),
474 cache_enabled: true, eval_lock: Mutex::new(()),
476 cached_msgpack_schema: None, };
478
479 Ok(instance)
480 }
481
482 pub fn reload_schema(
483 &mut self,
484 schema: &str,
485 context: Option<&str>,
486 data: Option<&str>,
487 ) -> Result<(), String> {
488 let schema_val: Value = serde_json::from_str(schema).map_err(|e| format!("failed to parse schema: {e}"))?;
490 let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))?;
491 let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))?;
492 self.schema = Arc::new(schema_val);
493 self.context = context.clone();
494 self.data = data.clone();
495 self.evaluated_schema = (*self.schema).clone();
496 self.engine = Arc::new(RLogic::new());
497 self.dependents_evaluations = Arc::new(IndexMap::new());
498 self.rules_evaluations = Arc::new(Vec::new());
499 self.fields_with_rules = Arc::new(Vec::new());
500 self.others_evaluations = Arc::new(Vec::new());
501 self.value_evaluations = Arc::new(Vec::new());
502 self.layout_paths = Arc::new(Vec::new());
503 self.options_templates = Arc::new(Vec::new());
504 self.subforms.clear();
505 parse_schema::legacy::parse_schema(self)?;
506
507 self.eval_data = EvalData::with_schema_data_context(&self.evaluated_schema, &data, &context);
509
510 self.eval_cache.clear();
512
513 self.cached_msgpack_schema = None;
515
516 Ok(())
517 }
518
519 pub fn set_timezone_offset(&mut self, offset_minutes: Option<i32>) {
541 let mut config = RLogicConfig::default();
543 if let Some(offset) = offset_minutes {
544 config = config.with_timezone_offset(offset);
545 }
546
547 self.engine = Arc::new(RLogic::with_config(config));
550
551 let _ = parse_schema::legacy::parse_schema(self);
554
555 self.eval_cache.clear();
557 }
558
559 pub fn reload_schema_msgpack(
571 &mut self,
572 schema_msgpack: &[u8],
573 context: Option<&str>,
574 data: Option<&str>,
575 ) -> Result<(), String> {
576 let schema_val: Value = rmp_serde::from_slice(schema_msgpack)
578 .map_err(|e| format!("failed to deserialize MessagePack schema: {e}"))?;
579
580 let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))?;
581 let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))?;
582
583 self.schema = Arc::new(schema_val);
584 self.context = context.clone();
585 self.data = data.clone();
586 self.evaluated_schema = (*self.schema).clone();
587 self.engine = Arc::new(RLogic::new());
588 self.dependents_evaluations = Arc::new(IndexMap::new());
589 self.rules_evaluations = Arc::new(Vec::new());
590 self.fields_with_rules = Arc::new(Vec::new());
591 self.others_evaluations = Arc::new(Vec::new());
592 self.value_evaluations = Arc::new(Vec::new());
593 self.layout_paths = Arc::new(Vec::new());
594 self.options_templates = Arc::new(Vec::new());
595 self.subforms.clear();
596 parse_schema::legacy::parse_schema(self)?;
597
598 self.eval_data = EvalData::with_schema_data_context(&self.evaluated_schema, &data, &context);
600
601 self.eval_cache.clear();
603
604 self.cached_msgpack_schema = Some(schema_msgpack.to_vec());
606
607 Ok(())
608 }
609
610 pub fn reload_schema_parsed(
624 &mut self,
625 parsed: Arc<ParsedSchema>,
626 context: Option<&str>,
627 data: Option<&str>,
628 ) -> Result<(), String> {
629 let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))?;
630 let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))?;
631
632 self.schema = Arc::clone(&parsed.schema);
634 self.evaluations = parsed.evaluations.clone();
635 self.tables = parsed.tables.clone();
636 self.table_metadata = parsed.table_metadata.clone();
637 self.dependencies = parsed.dependencies.clone();
638 self.sorted_evaluations = parsed.sorted_evaluations.clone();
639 self.dependents_evaluations = parsed.dependents_evaluations.clone();
640 self.rules_evaluations = parsed.rules_evaluations.clone();
641 self.fields_with_rules = parsed.fields_with_rules.clone();
642 self.others_evaluations = parsed.others_evaluations.clone();
643 self.value_evaluations = parsed.value_evaluations.clone();
644 self.layout_paths = parsed.layout_paths.clone();
645 self.options_templates = parsed.options_templates.clone();
646
647 self.engine = parsed.engine.clone();
649
650 let mut subforms = IndexMap::new();
652 for (path, subform_parsed) in &parsed.subforms {
653 let subform_eval = JSONEval::with_parsed_schema(
654 subform_parsed.clone(),
655 Some("{}"),
656 None
657 )?;
658 subforms.insert(path.clone(), Box::new(subform_eval));
659 }
660 self.subforms = subforms;
661
662 self.context = context.clone();
663 self.data = data.clone();
664 self.evaluated_schema = (*self.schema).clone();
665
666 self.eval_data = EvalData::with_schema_data_context(&self.evaluated_schema, &data, &context);
668
669 self.eval_cache.clear();
671
672 self.cached_msgpack_schema = None;
674
675 Ok(())
676 }
677
678 pub fn reload_schema_from_cache(
692 &mut self,
693 cache_key: &str,
694 context: Option<&str>,
695 data: Option<&str>,
696 ) -> Result<(), String> {
697 let parsed = PARSED_SCHEMA_CACHE.get(cache_key)
699 .ok_or_else(|| format!("Schema '{}' not found in cache", cache_key))?;
700
701 self.reload_schema_parsed(parsed, context, data)
703 }
704
705 pub fn evaluate(&mut self, data: &str, context: Option<&str>, paths: Option<&[String]>) -> Result<(), String> {
716 time_block!("evaluate() [total]", {
717 let context_provided = context.is_some();
718
719 let data: Value = time_block!(" parse data", {
721 json_parser::parse_json_str(data)?
722 });
723 let context: Value = time_block!(" parse context", {
724 json_parser::parse_json_str(context.unwrap_or("{}"))?
725 });
726
727 self.data = data.clone();
728
729 let changed_data_paths: Vec<String> = if let Some(obj) = data.as_object() {
731 obj.keys().map(|k| format!("/{}", k)).collect()
732 } else {
733 Vec::new()
734 };
735
736 time_block!(" replace_data_and_context", {
738 self.eval_data.replace_data_and_context(data, context);
739 });
740
741 time_block!(" purge_cache", {
744 self.purge_cache_for_changed_data(&changed_data_paths);
745
746 if context_provided {
748 self.purge_cache_for_context_change();
749 }
750 });
751
752 self.evaluate_internal(paths)
754 })
755 }
756
757 fn evaluate_internal(&mut self, paths: Option<&[String]>) -> Result<(), String> {
760 time_block!(" evaluate_internal() [total]", {
761 let _lock = self.eval_lock.lock().unwrap();
763
764 let normalized_paths_storage; let normalized_paths = if let Some(p_list) = paths {
767 normalized_paths_storage = p_list.iter()
768 .flat_map(|p| {
769 let normalized = if p.starts_with("#/") {
770 p.to_string()
772 } else if p.starts_with('/') {
773 format!("#{}", p)
775 } else {
776 format!("#/{}", p.replace('.', "/"))
778 };
779
780 vec![normalized]
781 })
782 .collect::<Vec<_>>();
783 Some(normalized_paths_storage.as_slice())
784 } else {
785 None
786 };
787
788 let eval_batches: Vec<Vec<String>> = (*self.sorted_evaluations).clone();
790
791 let eval_data_values = self.eval_data.clone();
796 time_block!(" evaluate values", {
797 #[cfg(feature = "parallel")]
798 if self.value_evaluations.len() > 100 {
799 let value_results: Mutex<Vec<(String, Value)>> = Mutex::new(Vec::with_capacity(self.value_evaluations.len()));
800
801 self.value_evaluations.par_iter().for_each(|eval_key| {
802 if let Some(deps) = self.dependencies.get(eval_key) {
804 if !deps.is_empty() {
805 return;
806 }
807 }
808
809 if let Some(filter_paths) = normalized_paths {
811 if !filter_paths.is_empty() && !filter_paths.iter().any(|p| eval_key.starts_with(p.as_str()) || p.starts_with(eval_key.as_str())) {
812 return;
813 }
814 }
815
816 let pointer_path = path_utils::normalize_to_json_pointer(eval_key);
819
820 if let Some(_) = self.try_get_cached(eval_key, &eval_data_values) {
822 return;
823 }
824
825 if let Some(logic_id) = self.evaluations.get(eval_key) {
827 if let Ok(val) = self.engine.run(logic_id, eval_data_values.data()) {
828 let cleaned_val = clean_float_noise(val);
829 self.cache_result(eval_key, Value::Null, &eval_data_values);
831 value_results.lock().unwrap().push((pointer_path, cleaned_val));
832 }
833 }
834 });
835
836 for (result_path, value) in value_results.into_inner().unwrap() {
838 if let Some(pointer_value) = self.evaluated_schema.pointer_mut(&result_path) {
839 *pointer_value = value;
840 }
841 }
842 }
843
844 #[cfg(feature = "parallel")]
846 let value_eval_items = if self.value_evaluations.len() > 100 { &self.value_evaluations[0..0] } else { &self.value_evaluations };
847
848 #[cfg(not(feature = "parallel"))]
849 let value_eval_items = &self.value_evaluations;
850
851 for eval_key in value_eval_items.iter() {
852 if let Some(deps) = self.dependencies.get(eval_key) {
854 if !deps.is_empty() {
855 continue;
856 }
857 }
858
859 if let Some(filter_paths) = normalized_paths {
861 if !filter_paths.is_empty() && !filter_paths.iter().any(|p| eval_key.starts_with(p.as_str()) || p.starts_with(eval_key.as_str())) {
862 continue;
863 }
864 }
865
866 let pointer_path = path_utils::normalize_to_json_pointer(eval_key);
867
868 if let Some(_) = self.try_get_cached(eval_key, &eval_data_values) {
870 continue;
871 }
872
873 if let Some(logic_id) = self.evaluations.get(eval_key) {
875 if let Ok(val) = self.engine.run(logic_id, eval_data_values.data()) {
876 let cleaned_val = clean_float_noise(val);
877 self.cache_result(eval_key, Value::Null, &eval_data_values);
879
880 if let Some(pointer_value) = self.evaluated_schema.pointer_mut(&pointer_path) {
881 *pointer_value = cleaned_val;
882 }
883 }
884 }
885 }
886 });
887
888 time_block!(" process batches", {
889 for batch in eval_batches {
890 if batch.is_empty() {
892 continue;
893 }
894
895 if let Some(filter_paths) = normalized_paths {
898 if !filter_paths.is_empty() {
899 let batch_has_match = batch.iter().any(|eval_key| {
900 filter_paths.iter().any(|p| eval_key.starts_with(p.as_str()) || p.starts_with(eval_key.as_str()))
901 });
902 if !batch_has_match {
903 continue;
904 }
905 }
906 }
907
908 let eval_data_snapshot = self.eval_data.clone();
915
916 #[cfg(feature = "parallel")]
920 if batch.len() > 1000 {
921 let results: Mutex<Vec<(String, String, Value)>> = Mutex::new(Vec::with_capacity(batch.len()));
922 batch.par_iter().for_each(|eval_key| {
923 if let Some(filter_paths) = normalized_paths {
925 if !filter_paths.is_empty() && !filter_paths.iter().any(|p| eval_key.starts_with(p.as_str()) || p.starts_with(eval_key.as_str())) {
926 return;
927 }
928 }
929
930 let pointer_path = path_utils::normalize_to_json_pointer(eval_key);
931
932 if let Some(_) = self.try_get_cached(eval_key, &eval_data_snapshot) {
934 return;
935 }
936
937 let is_table = self.table_metadata.contains_key(eval_key);
939
940 if is_table {
941 if let Ok(rows) = table_evaluate::evaluate_table(self, eval_key, &eval_data_snapshot) {
943 let value = Value::Array(rows);
944 self.cache_result(eval_key, Value::Null, &eval_data_snapshot);
946 results.lock().unwrap().push((eval_key.clone(), pointer_path, value));
947 }
948 } else {
949 if let Some(logic_id) = self.evaluations.get(eval_key) {
950 if let Ok(val) = self.engine.run(logic_id, eval_data_snapshot.data()) {
952 let cleaned_val = clean_float_noise(val);
953 self.cache_result(eval_key, Value::Null, &eval_data_snapshot);
955 results.lock().unwrap().push((eval_key.clone(), pointer_path, cleaned_val));
956 }
957 }
958 }
959 });
960
961 for (_eval_key, path, value) in results.into_inner().unwrap() {
963 let cleaned_value = clean_float_noise(value);
964
965 self.eval_data.set(&path, cleaned_value.clone());
966 if let Some(schema_value) = self.evaluated_schema.pointer_mut(&path) {
968 *schema_value = cleaned_value;
969 }
970 }
971 continue;
972 }
973
974 #[cfg(not(feature = "parallel"))]
976 let batch_items = &batch;
977
978 #[cfg(feature = "parallel")]
979 let batch_items = if batch.len() > 1000 { &batch[0..0] } else { &batch }; for eval_key in batch_items {
982 if let Some(filter_paths) = normalized_paths {
984 if !filter_paths.is_empty() && !filter_paths.iter().any(|p| eval_key.starts_with(p.as_str()) || p.starts_with(eval_key.as_str())) {
985 continue;
986 }
987 }
988
989 let pointer_path = path_utils::normalize_to_json_pointer(eval_key);
990
991 if let Some(_) = self.try_get_cached(eval_key, &eval_data_snapshot) {
993 continue;
994 }
995
996 let is_table = self.table_metadata.contains_key(eval_key);
998
999 if is_table {
1000 if let Ok(rows) = table_evaluate::evaluate_table(self, eval_key, &eval_data_snapshot) {
1001 let value = Value::Array(rows);
1002 self.cache_result(eval_key, Value::Null, &eval_data_snapshot);
1004
1005 let cleaned_value = clean_float_noise(value);
1006 self.eval_data.set(&pointer_path, cleaned_value.clone());
1007 if let Some(schema_value) = self.evaluated_schema.pointer_mut(&pointer_path) {
1008 *schema_value = cleaned_value;
1009 }
1010 }
1011 } else {
1012 if let Some(logic_id) = self.evaluations.get(eval_key) {
1013 if let Ok(val) = self.engine.run(logic_id, eval_data_snapshot.data()) {
1014 let cleaned_val = clean_float_noise(val);
1015 self.cache_result(eval_key, Value::Null, &eval_data_snapshot);
1017
1018 self.eval_data.set(&pointer_path, cleaned_val.clone());
1019 if let Some(schema_value) = self.evaluated_schema.pointer_mut(&pointer_path) {
1020 *schema_value = cleaned_val;
1021 }
1022 }
1023 }
1024 }
1025 }
1026 }
1027 });
1028
1029 drop(_lock);
1031
1032 self.evaluate_others(paths);
1033
1034 Ok(())
1035 })
1036 }
1037
1038 pub fn get_evaluated_schema(&mut self, skip_layout: bool) -> Value {
1048 time_block!("get_evaluated_schema()", {
1049 if !skip_layout {
1050 self.resolve_layout_internal();
1051 }
1052
1053 self.evaluated_schema.clone()
1054 })
1055 }
1056
1057 pub fn get_evaluated_schema_msgpack(&mut self, skip_layout: bool) -> Result<Vec<u8>, String> {
1074 if !skip_layout {
1075 self.resolve_layout_internal();
1076 }
1077
1078 rmp_serde::to_vec(&self.evaluated_schema)
1082 .map_err(|e| format!("Failed to serialize schema to MessagePack: {}", e))
1083 }
1084
1085 pub fn get_schema_value(&mut self) -> Value {
1089 if !self.data.is_object() {
1091 self.data = Value::Object(serde_json::Map::new());
1092 }
1093
1094 for eval_key in self.value_evaluations.iter() {
1096 let clean_key = eval_key.replace("#", "");
1097
1098 if clean_key.starts_with("/$params") || (clean_key.ends_with("/value") && (clean_key.contains("/rules/") || clean_key.contains("/options/"))) {
1100 continue;
1101 }
1102
1103 let path = clean_key.replace("/properties", "").replace("/value", "");
1104
1105 let value = match self.evaluated_schema.pointer(&clean_key) {
1107 Some(v) => v.clone(),
1108 None => continue,
1109 };
1110
1111 let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
1113
1114 if path_parts.is_empty() {
1115 continue;
1116 }
1117
1118 let mut current = &mut self.data;
1120 for (i, part) in path_parts.iter().enumerate() {
1121 let is_last = i == path_parts.len() - 1;
1122
1123 if is_last {
1124 if let Some(obj) = current.as_object_mut() {
1126 obj.insert(part.to_string(), clean_float_noise(value.clone()));
1127 }
1128 } else {
1129 if let Some(obj) = current.as_object_mut() {
1131 current = obj.entry(part.to_string())
1132 .or_insert_with(|| Value::Object(serde_json::Map::new()));
1133 } else {
1134 break;
1136 }
1137 }
1138 }
1139 }
1140
1141 clean_float_noise(self.data.clone())
1142 }
1143
1144 pub fn get_evaluated_schema_without_params(&mut self, skip_layout: bool) -> Value {
1155 if !skip_layout {
1156 self.resolve_layout_internal();
1157 }
1158
1159 if let Value::Object(mut map) = self.evaluated_schema.clone() {
1161 map.remove("$params");
1162 Value::Object(map)
1163 } else {
1164 self.evaluated_schema.clone()
1165 }
1166 }
1167
1168 pub fn get_evaluated_schema_by_path(&mut self, path: &str, skip_layout: bool) -> Option<Value> {
1180 if !skip_layout {
1181 self.resolve_layout_internal();
1182 }
1183
1184 let pointer = if path.is_empty() {
1186 "".to_string()
1187 } else {
1188 format!("/{}", path.replace(".", "/"))
1189 };
1190
1191 self.evaluated_schema.pointer(&pointer).cloned()
1192 }
1193
1194 pub fn get_evaluated_schema_by_paths(&mut self, paths: &[String], skip_layout: bool, format: Option<ReturnFormat>) -> Value {
1207 let format = format.unwrap_or_default();
1208 if !skip_layout {
1209 self.resolve_layout_internal();
1210 }
1211
1212 let mut result = serde_json::Map::new();
1213
1214 for path in paths {
1215 let pointer = if path.is_empty() {
1217 "".to_string()
1218 } else {
1219 format!("/{}", path.replace(".", "/"))
1220 };
1221
1222 if let Some(value) = self.evaluated_schema.pointer(&pointer) {
1224 self.insert_at_path(&mut result, path, value.clone());
1227 }
1228 }
1229
1230 self.convert_to_format(result, paths, format)
1231 }
1232
1233 fn insert_at_path(&self, obj: &mut serde_json::Map<String, Value>, path: &str, value: Value) {
1235 if path.is_empty() {
1236 if let Value::Object(map) = value {
1238 for (k, v) in map {
1239 obj.insert(k, v);
1240 }
1241 }
1242 return;
1243 }
1244
1245 let parts: Vec<&str> = path.split('.').collect();
1246 if parts.is_empty() {
1247 return;
1248 }
1249
1250 let mut current = obj;
1251 let last_index = parts.len() - 1;
1252
1253 for (i, part) in parts.iter().enumerate() {
1254 if i == last_index {
1255 current.insert(part.to_string(), value);
1257 break;
1258 } else {
1259 current = current
1261 .entry(part.to_string())
1262 .or_insert_with(|| Value::Object(serde_json::Map::new()))
1263 .as_object_mut()
1264 .unwrap();
1265 }
1266 }
1267 }
1268
1269 fn convert_to_format(&self, result: serde_json::Map<String, Value>, paths: &[String], format: ReturnFormat) -> Value {
1271 match format {
1272 ReturnFormat::Nested => Value::Object(result),
1273 ReturnFormat::Flat => {
1274 let mut flat = serde_json::Map::new();
1276 self.flatten_object(&result, String::new(), &mut flat);
1277 Value::Object(flat)
1278 }
1279 ReturnFormat::Array => {
1280 let values: Vec<Value> = paths.iter()
1282 .map(|path| {
1283 let pointer = if path.is_empty() {
1284 "".to_string()
1285 } else {
1286 format!("/{}", path.replace(".", "/"))
1287 };
1288 Value::Object(result.clone()).pointer(&pointer).cloned().unwrap_or(Value::Null)
1289 })
1290 .collect();
1291 Value::Array(values)
1292 }
1293 }
1294 }
1295
1296 fn flatten_object(&self, obj: &serde_json::Map<String, Value>, prefix: String, result: &mut serde_json::Map<String, Value>) {
1298 for (key, value) in obj {
1299 let new_key = if prefix.is_empty() {
1300 key.clone()
1301 } else {
1302 format!("{}.{}", prefix, key)
1303 };
1304
1305 if let Value::Object(nested) = value {
1306 self.flatten_object(nested, new_key, result);
1307 } else {
1308 result.insert(new_key, value.clone());
1309 }
1310 }
1311 }
1312
1313 pub fn get_schema_by_path(&self, path: &str) -> Option<Value> {
1324 let pointer = if path.is_empty() {
1326 "".to_string()
1327 } else {
1328 format!("/{}", path.replace(".", "/"))
1329 };
1330
1331 self.schema.pointer(&pointer).cloned()
1332 }
1333
1334 pub fn get_schema_by_paths(&self, paths: &[String], format: Option<ReturnFormat>) -> Value {
1346 let format = format.unwrap_or_default();
1347 let mut result = serde_json::Map::new();
1348
1349 for path in paths {
1350 let pointer = if path.is_empty() {
1352 "".to_string()
1353 } else {
1354 format!("/{}", path.replace(".", "/"))
1355 };
1356
1357 if let Some(value) = self.schema.pointer(&pointer) {
1359 self.insert_at_path(&mut result, path, value.clone());
1362 }
1363 }
1364
1365 self.convert_to_format(result, paths, format)
1366 }
1367
1368 #[inline]
1371 fn should_cache_dependency(key: &str) -> bool {
1372 if key.starts_with("/$") || key.starts_with('$') {
1373 key == "$context" || key.starts_with("$context.") || key.starts_with("/$context")
1375 } else {
1376 true
1377 }
1378 }
1379
1380 fn try_get_cached(&self, eval_key: &str, eval_data: &EvalData) -> Option<Value> {
1383 if !self.cache_enabled {
1385 return None;
1386 }
1387
1388 let deps = self.dependencies.get(eval_key)?;
1390
1391 let cache_key = if deps.is_empty() {
1393 CacheKey::simple(eval_key.to_string())
1394 } else {
1395 let filtered_deps: IndexSet<String> = deps
1397 .iter()
1398 .filter(|dep_key| JSONEval::should_cache_dependency(dep_key))
1399 .cloned()
1400 .collect();
1401
1402 let dep_values: Vec<(String, &Value)> = filtered_deps
1404 .iter()
1405 .filter_map(|dep_key| {
1406 eval_data.get(dep_key).map(|v| (dep_key.clone(), v))
1407 })
1408 .collect();
1409
1410 CacheKey::new(eval_key.to_string(), &filtered_deps, &dep_values)
1411 };
1412
1413 self.eval_cache.get(&cache_key).map(|arc_val| (*arc_val).clone())
1415 }
1416
1417 fn cache_result(&self, eval_key: &str, value: Value, eval_data: &EvalData) {
1419 if !self.cache_enabled {
1421 return;
1422 }
1423
1424 let deps = match self.dependencies.get(eval_key) {
1426 Some(d) => d,
1427 None => {
1428 let cache_key = CacheKey::simple(eval_key.to_string());
1430 self.eval_cache.insert(cache_key, value);
1431 return;
1432 }
1433 };
1434
1435 let filtered_deps: IndexSet<String> = deps
1437 .iter()
1438 .filter(|dep_key| JSONEval::should_cache_dependency(dep_key))
1439 .cloned()
1440 .collect();
1441
1442 let dep_values: Vec<(String, &Value)> = filtered_deps
1443 .iter()
1444 .filter_map(|dep_key| {
1445 eval_data.get(dep_key).map(|v| (dep_key.clone(), v))
1446 })
1447 .collect();
1448
1449 let cache_key = CacheKey::new(eval_key.to_string(), &filtered_deps, &dep_values);
1450 self.eval_cache.insert(cache_key, value);
1451 }
1452
1453 fn purge_cache_for_changed_data_with_comparison(
1457 &self,
1458 changed_data_paths: &[String],
1459 old_data: &Value,
1460 new_data: &Value
1461 ) {
1462 if changed_data_paths.is_empty() {
1463 return;
1464 }
1465
1466 let mut actually_changed_paths = Vec::new();
1468 for path in changed_data_paths {
1469 let old_val = old_data.pointer(path);
1470 let new_val = new_data.pointer(path);
1471
1472 if old_val != new_val {
1474 actually_changed_paths.push(path.clone());
1475 }
1476 }
1477
1478 if actually_changed_paths.is_empty() {
1480 return;
1481 }
1482
1483 let mut affected_eval_keys = IndexSet::new();
1485
1486 for (eval_key, deps) in self.dependencies.iter() {
1487 let is_affected = deps.iter().any(|dep| {
1489 actually_changed_paths.iter().any(|changed_path| {
1491 dep == changed_path ||
1493 dep.starts_with(&format!("{}/", changed_path)) ||
1494 changed_path.starts_with(&format!("{}/", dep))
1495 })
1496 });
1497
1498 if is_affected {
1499 affected_eval_keys.insert(eval_key.clone());
1500 }
1501 }
1502
1503 self.eval_cache.retain(|cache_key, _| {
1506 !affected_eval_keys.contains(&cache_key.eval_key)
1507 });
1508 }
1509
1510 fn purge_cache_for_changed_data(&self, changed_data_paths: &[String]) {
1513 if changed_data_paths.is_empty() {
1514 return;
1515 }
1516
1517 let changed_schema_paths: Vec<String> = changed_data_paths.iter()
1520 .map(|data_path| {
1521 let cleaned = data_path.trim_start_matches('/');
1524 if cleaned.is_empty() {
1525 "/properties".to_string()
1526 } else {
1527 let segments: Vec<&str> = cleaned.split('/').collect();
1529 let schema_path = segments.join("/properties/");
1530 format!("/properties/{}", schema_path)
1531 }
1532 })
1533 .collect();
1534
1535 let mut affected_eval_keys = IndexSet::new();
1537
1538 for (eval_key, deps) in self.dependencies.iter() {
1539 let is_affected = deps.iter().any(|dep| {
1541 changed_data_paths.iter().any(|changed_data_path| {
1543 dep == changed_data_path ||
1545 dep.starts_with(&format!("{}/", changed_data_path)) ||
1546 changed_data_path.starts_with(&format!("{}/", dep))
1547 }) ||
1548 changed_schema_paths.iter().any(|changed_schema_path| {
1549 dep == changed_schema_path ||
1551 dep.starts_with(&format!("{}/", changed_schema_path)) ||
1552 changed_schema_path.starts_with(&format!("{}/", dep))
1553 })
1554 });
1555
1556 if is_affected {
1557 affected_eval_keys.insert(eval_key.clone());
1558 }
1559 }
1560
1561 self.eval_cache.retain(|cache_key, _| {
1564 !affected_eval_keys.contains(&cache_key.eval_key)
1565 });
1566 }
1567
1568 fn purge_cache_for_context_change(&self) {
1570 let mut affected_eval_keys = IndexSet::new();
1572
1573 for (eval_key, deps) in self.dependencies.iter() {
1574 let is_affected = deps.iter().any(|dep| {
1575 dep == "$context" || dep.starts_with("$context.") || dep.starts_with("/$context")
1576 });
1577
1578 if is_affected {
1579 affected_eval_keys.insert(eval_key.clone());
1580 }
1581 }
1582
1583 self.eval_cache.retain(|cache_key, _| {
1584 !affected_eval_keys.contains(&cache_key.eval_key)
1585 });
1586 }
1587
1588 pub fn cache_stats(&self) -> CacheStats {
1590 self.eval_cache.stats()
1591 }
1592
1593 pub fn clear_cache(&mut self) {
1595 self.eval_cache.clear();
1596 for subform in self.subforms.values_mut() {
1597 subform.clear_cache();
1598 }
1599 }
1600
1601 pub fn cache_len(&self) -> usize {
1603 self.eval_cache.len()
1604 }
1605
1606 pub fn enable_cache(&mut self) {
1609 self.cache_enabled = true;
1610 for subform in self.subforms.values_mut() {
1611 subform.enable_cache();
1612 }
1613 }
1614
1615 pub fn disable_cache(&mut self) {
1619 self.cache_enabled = false;
1620 self.eval_cache.clear(); for subform in self.subforms.values_mut() {
1622 subform.disable_cache();
1623 }
1624 }
1625
1626 pub fn is_cache_enabled(&self) -> bool {
1628 self.cache_enabled
1629 }
1630
1631 fn evaluate_others(&mut self, paths: Option<&[String]>) {
1632 time_block!(" evaluate_others()", {
1633 time_block!(" evaluate_options_templates", {
1635 self.evaluate_options_templates(paths);
1636 });
1637
1638 let combined_count = self.rules_evaluations.len() + self.others_evaluations.len();
1641 if combined_count == 0 {
1642 return;
1643 }
1644
1645 time_block!(" evaluate rules+others", {
1646 let eval_data_snapshot = self.eval_data.clone();
1647
1648 let normalized_paths: Option<Vec<String>> = paths.map(|p_list| {
1649 p_list.iter()
1650 .flat_map(|p| {
1651 let ptr = path_utils::dot_notation_to_schema_pointer(p);
1652 let with_props = if ptr.starts_with("#/") {
1654 format!("#/properties/{}", &ptr[2..])
1655 } else {
1656 ptr.clone()
1657 };
1658 vec![ptr, with_props]
1659 })
1660 .collect()
1661 });
1662
1663 #[cfg(feature = "parallel")]
1664 {
1665 let combined_results: Mutex<Vec<(String, Value)>> = Mutex::new(Vec::with_capacity(combined_count));
1666
1667 self.rules_evaluations
1668 .par_iter()
1669 .chain(self.others_evaluations.par_iter())
1670 .for_each(|eval_key| {
1671 if let Some(filter_paths) = normalized_paths.as_ref() {
1673 if !filter_paths.is_empty() && !filter_paths.iter().any(|p| eval_key.starts_with(p.as_str()) || p.starts_with(eval_key.as_str())) {
1674 return;
1675 }
1676 }
1677
1678 let pointer_path = path_utils::normalize_to_json_pointer(eval_key);
1679
1680 if let Some(_) = self.try_get_cached(eval_key, &eval_data_snapshot) {
1682 return;
1683 }
1684
1685 if let Some(logic_id) = self.evaluations.get(eval_key) {
1687 if let Ok(val) = self.engine.run(logic_id, eval_data_snapshot.data()) {
1688 let cleaned_val = clean_float_noise(val);
1689 self.cache_result(eval_key, Value::Null, &eval_data_snapshot);
1691 combined_results.lock().unwrap().push((pointer_path, cleaned_val));
1692 }
1693 }
1694 });
1695
1696 for (result_path, value) in combined_results.into_inner().unwrap() {
1698 if let Some(pointer_value) = self.evaluated_schema.pointer_mut(&result_path) {
1699 if !result_path.starts_with("$") && result_path.contains("/rules/") && !result_path.ends_with("/value") {
1702 match pointer_value.as_object_mut() {
1703 Some(pointer_obj) => {
1704 pointer_obj.remove("$evaluation");
1705 pointer_obj.insert("value".to_string(), value);
1706 },
1707 None => continue,
1708 }
1709 } else {
1710 *pointer_value = value;
1711 }
1712 }
1713 }
1714 }
1715
1716 #[cfg(not(feature = "parallel"))]
1717 {
1718 let combined_evals: Vec<&String> = self.rules_evaluations.iter()
1720 .chain(self.others_evaluations.iter())
1721 .collect();
1722
1723 for eval_key in combined_evals {
1724 if let Some(filter_paths) = normalized_paths.as_ref() {
1726 if !filter_paths.is_empty() && !filter_paths.iter().any(|p| eval_key.starts_with(p.as_str()) || p.starts_with(eval_key.as_str())) {
1727 continue;
1728 }
1729 }
1730
1731 let pointer_path = path_utils::normalize_to_json_pointer(eval_key);
1732
1733 if let Some(_) = self.try_get_cached(eval_key, &eval_data_snapshot) {
1735 continue;
1736 }
1737
1738 if let Some(logic_id) = self.evaluations.get(eval_key) {
1740 if let Ok(val) = self.engine.run(logic_id, eval_data_snapshot.data()) {
1741 let cleaned_val = clean_float_noise(val);
1742 self.cache_result(eval_key, Value::Null, &eval_data_snapshot);
1744
1745 if let Some(pointer_value) = self.evaluated_schema.pointer_mut(&pointer_path) {
1746 if !pointer_path.starts_with("$") && pointer_path.contains("/rules/") && !pointer_path.ends_with("/value") {
1747 match pointer_value.as_object_mut() {
1748 Some(pointer_obj) => {
1749 pointer_obj.remove("$evaluation");
1750 pointer_obj.insert("value".to_string(), cleaned_val);
1751 },
1752 None => continue,
1753 }
1754 } else {
1755 *pointer_value = cleaned_val;
1756 }
1757 }
1758 }
1759 }
1760 }
1761 }
1762 });
1763 });
1764 }
1765
1766 fn evaluate_options_templates(&mut self, paths: Option<&[String]>) {
1768 let templates_to_eval = self.options_templates.clone();
1770
1771 for (path, template_str, params_path) in templates_to_eval.iter() {
1773 if let Some(filter_paths) = paths {
1777 if !filter_paths.is_empty() && !filter_paths.iter().any(|p| path.starts_with(p.as_str()) || p.starts_with(path.as_str())) {
1778 continue;
1779 }
1780 }
1781
1782 if let Some(params) = self.evaluated_schema.pointer(¶ms_path) {
1783 if let Ok(evaluated) = self.evaluate_template(&template_str, params) {
1784 if let Some(target) = self.evaluated_schema.pointer_mut(&path) {
1785 *target = Value::String(evaluated);
1786 }
1787 }
1788 }
1789 }
1790 }
1791
1792 fn evaluate_template(&self, template: &str, params: &Value) -> Result<String, String> {
1794 let mut result = template.to_string();
1795
1796 if let Value::Object(params_map) = params {
1798 for (key, value) in params_map {
1799 let placeholder = format!("{{{}}}", key);
1800 if let Some(str_val) = value.as_str() {
1801 result = result.replace(&placeholder, str_val);
1802 } else {
1803 result = result.replace(&placeholder, &value.to_string());
1805 }
1806 }
1807 }
1808
1809 Ok(result)
1810 }
1811
1812 pub fn compile_logic(&self, logic_str: &str) -> Result<CompiledLogicId, String> {
1828 rlogic::compiled_logic_store::compile_logic(logic_str)
1829 }
1830
1831 pub fn compile_logic_value(&self, logic: &Value) -> Result<CompiledLogicId, String> {
1848 rlogic::compiled_logic_store::compile_logic_value(logic)
1849 }
1850
1851 pub fn run_logic(&mut self, logic_id: CompiledLogicId, data: Option<&Value>, context: Option<&Value>) -> Result<Value, String> {
1868 let compiled_logic = rlogic::compiled_logic_store::get_compiled_logic(logic_id)
1870 .ok_or_else(|| format!("Compiled logic ID {:?} not found in store", logic_id))?;
1871
1872 let eval_data_value = if let Some(input_data) = data {
1876 let context_value = context.unwrap_or(&self.context);
1877
1878 self.eval_data.replace_data_and_context(input_data.clone(), context_value.clone());
1879 self.eval_data.data()
1880 } else {
1881 self.eval_data.data()
1882 };
1883
1884 let evaluator = Evaluator::new();
1886 let result = evaluator.evaluate(&compiled_logic, &eval_data_value)?;
1887
1888 Ok(clean_float_noise(result))
1889 }
1890
1891 pub fn compile_and_run_logic(&mut self, logic_str: &str, data: Option<&str>, context: Option<&str>) -> Result<Value, String> {
1907 let compiled_logic = self.compile_logic(logic_str)?;
1909
1910 let data_value = if let Some(data_str) = data {
1912 Some(json_parser::parse_json_str(data_str)?)
1913 } else {
1914 None
1915 };
1916
1917 let context_value = if let Some(ctx_str) = context {
1918 Some(json_parser::parse_json_str(ctx_str)?)
1919 } else {
1920 None
1921 };
1922
1923 self.run_logic(compiled_logic, data_value.as_ref(), context_value.as_ref())
1925 }
1926
1927 pub fn resolve_layout(&mut self, evaluate: bool) -> Result<(), String> {
1937 if evaluate {
1938 let data_str = serde_json::to_string(&self.data)
1940 .map_err(|e| format!("Failed to serialize data: {}", e))?;
1941 self.evaluate(&data_str, None, None)?;
1942 }
1943
1944 self.resolve_layout_internal();
1945 Ok(())
1946 }
1947
1948 fn resolve_layout_internal(&mut self) {
1949 time_block!(" resolve_layout_internal()", {
1950 let layout_paths = self.layout_paths.clone();
1953
1954 time_block!(" resolve_layout_elements", {
1955 for layout_path in layout_paths.iter() {
1956 self.resolve_layout_elements(layout_path);
1957 }
1958 });
1959
1960 time_block!(" propagate_parent_conditions", {
1962 for layout_path in layout_paths.iter() {
1963 self.propagate_parent_conditions(layout_path);
1964 }
1965 });
1966 });
1967 }
1968
1969 fn propagate_parent_conditions(&mut self, layout_elements_path: &str) {
1971 let normalized_path = path_utils::normalize_to_json_pointer(layout_elements_path);
1973
1974 let elements = if let Some(Value::Array(arr)) = self.evaluated_schema.pointer_mut(&normalized_path) {
1976 mem::take(arr)
1977 } else {
1978 return;
1979 };
1980
1981 let mut updated_elements = Vec::with_capacity(elements.len());
1983 for element in elements {
1984 updated_elements.push(self.apply_parent_conditions(element, false, false));
1985 }
1986
1987 if let Some(target) = self.evaluated_schema.pointer_mut(&normalized_path) {
1989 *target = Value::Array(updated_elements);
1990 }
1991 }
1992
1993 fn apply_parent_conditions(&self, element: Value, parent_hidden: bool, parent_disabled: bool) -> Value {
1995 if let Value::Object(mut map) = element {
1996 let mut element_hidden = parent_hidden;
1998 let mut element_disabled = parent_disabled;
1999
2000 if let Some(Value::Object(condition)) = map.get("condition") {
2002 if let Some(Value::Bool(hidden)) = condition.get("hidden") {
2003 element_hidden = element_hidden || *hidden;
2004 }
2005 if let Some(Value::Bool(disabled)) = condition.get("disabled") {
2006 element_disabled = element_disabled || *disabled;
2007 }
2008 }
2009
2010 if let Some(Value::Object(hide_layout)) = map.get("hideLayout") {
2012 if let Some(Value::Bool(all_hidden)) = hide_layout.get("all") {
2014 if *all_hidden {
2015 element_hidden = true;
2016 }
2017 }
2018 }
2019
2020 if parent_hidden || parent_disabled {
2022 if map.contains_key("condition") || map.contains_key("$ref") || map.contains_key("$fullpath") {
2024 let mut condition = if let Some(Value::Object(c)) = map.get("condition") {
2025 c.clone()
2026 } else {
2027 serde_json::Map::new()
2028 };
2029
2030 if parent_hidden {
2031 condition.insert("hidden".to_string(), Value::Bool(true));
2032 }
2033 if parent_disabled {
2034 condition.insert("disabled".to_string(), Value::Bool(true));
2035 }
2036
2037 map.insert("condition".to_string(), Value::Object(condition));
2038 }
2039
2040 if parent_hidden && (map.contains_key("hideLayout") || map.contains_key("type")) {
2042 let mut hide_layout = if let Some(Value::Object(h)) = map.get("hideLayout") {
2043 h.clone()
2044 } else {
2045 serde_json::Map::new()
2046 };
2047
2048 hide_layout.insert("all".to_string(), Value::Bool(true));
2050 map.insert("hideLayout".to_string(), Value::Object(hide_layout));
2051 }
2052 }
2053
2054 if map.contains_key("$parentHide") {
2057 map.insert("$parentHide".to_string(), Value::Bool(parent_hidden));
2058 }
2059
2060 if let Some(Value::Array(elements)) = map.get("elements") {
2062 let mut updated_children = Vec::with_capacity(elements.len());
2063 for child in elements {
2064 updated_children.push(self.apply_parent_conditions(
2065 child.clone(),
2066 element_hidden,
2067 element_disabled,
2068 ));
2069 }
2070 map.insert("elements".to_string(), Value::Array(updated_children));
2071 }
2072
2073 return Value::Object(map);
2074 }
2075
2076 element
2077 }
2078
2079 fn resolve_layout_elements(&mut self, layout_elements_path: &str) {
2081 let normalized_path = path_utils::normalize_to_json_pointer(layout_elements_path);
2083
2084 let elements = if let Some(Value::Array(arr)) = self.schema.pointer(&normalized_path) {
2088 arr.clone()
2089 } else {
2090 return;
2091 };
2092
2093 let parent_path = normalized_path
2095 .trim_start_matches('/')
2096 .replace("/elements", "")
2097 .replace('/', ".");
2098
2099 let mut resolved_elements = Vec::with_capacity(elements.len());
2101 for (index, element) in elements.iter().enumerate() {
2102 let element_path = if parent_path.is_empty() {
2103 format!("elements.{}", index)
2104 } else {
2105 format!("{}.elements.{}", parent_path, index)
2106 };
2107 let resolved = self.resolve_element_ref_recursive(element.clone(), &element_path);
2108 resolved_elements.push(resolved);
2109 }
2110
2111 if let Some(target) = self.evaluated_schema.pointer_mut(&normalized_path) {
2113 *target = Value::Array(resolved_elements);
2114 }
2115 }
2116
2117 fn resolve_element_ref_recursive(&self, element: Value, path_context: &str) -> Value {
2120 let resolved = self.resolve_element_ref(element);
2122
2123 if let Value::Object(mut map) = resolved {
2125 if !map.contains_key("$parentHide") {
2129 map.insert("$parentHide".to_string(), Value::Bool(false));
2130 }
2131
2132 if !map.contains_key("$fullpath") {
2134 map.insert("$fullpath".to_string(), Value::String(path_context.to_string()));
2135 }
2136
2137 if !map.contains_key("$path") {
2138 let last_segment = path_context.split('.').last().unwrap_or(path_context);
2140 map.insert("$path".to_string(), Value::String(last_segment.to_string()));
2141 }
2142
2143 if let Some(Value::Array(elements)) = map.get("elements") {
2145 let mut resolved_nested = Vec::with_capacity(elements.len());
2146 for (index, nested_element) in elements.iter().enumerate() {
2147 let nested_path = format!("{}.elements.{}", path_context, index);
2148 resolved_nested.push(self.resolve_element_ref_recursive(nested_element.clone(), &nested_path));
2149 }
2150 map.insert("elements".to_string(), Value::Array(resolved_nested));
2151 }
2152
2153 return Value::Object(map);
2154 }
2155
2156 resolved
2157 }
2158
2159 fn resolve_element_ref(&self, element: Value) -> Value {
2161 match element {
2162 Value::Object(mut map) => {
2163 if let Some(Value::String(ref_path)) = map.get("$ref").cloned() {
2165 let dotted_path = path_utils::pointer_to_dot_notation(&ref_path);
2167
2168 let last_segment = dotted_path.split('.').last().unwrap_or(&dotted_path);
2170
2171 map.insert("$fullpath".to_string(), Value::String(dotted_path.clone()));
2173 map.insert("$path".to_string(), Value::String(last_segment.to_string()));
2174 map.insert("$parentHide".to_string(), Value::Bool(false));
2175
2176 let normalized_path = if ref_path.starts_with('#') || ref_path.starts_with('/') {
2179 path_utils::normalize_to_json_pointer(&ref_path)
2181 } else {
2182 let schema_pointer = path_utils::dot_notation_to_schema_pointer(&ref_path);
2184 let schema_path = path_utils::normalize_to_json_pointer(&schema_pointer);
2185
2186 if self.evaluated_schema.pointer(&schema_path).is_some() {
2188 schema_path
2189 } else {
2190 let with_properties = format!("/properties/{}", ref_path.replace('.', "/properties/"));
2192 with_properties
2193 }
2194 };
2195
2196 if let Some(referenced_value) = self.evaluated_schema.pointer(&normalized_path) {
2198 let resolved = referenced_value.clone();
2200
2201 if let Value::Object(mut resolved_map) = resolved {
2203 map.remove("$ref");
2205
2206 if let Some(Value::Object(layout_obj)) = resolved_map.remove("$layout") {
2209 let mut result = layout_obj.clone();
2211
2212 resolved_map.remove("properties");
2214
2215 for (key, value) in resolved_map {
2217 if key != "type" || !result.contains_key("type") {
2218 result.insert(key, value);
2219 }
2220 }
2221
2222 for (key, value) in map {
2224 result.insert(key, value);
2225 }
2226
2227 return Value::Object(result);
2228 } else {
2229 for (key, value) in map {
2231 resolved_map.insert(key, value);
2232 }
2233
2234 return Value::Object(resolved_map);
2235 }
2236 } else {
2237 return resolved;
2239 }
2240 }
2241 }
2242
2243 Value::Object(map)
2245 }
2246 _ => element,
2247 }
2248 }
2249
2250 pub fn evaluate_dependents(
2259 &mut self,
2260 changed_paths: &[String],
2261 data: Option<&str>,
2262 context: Option<&str>,
2263 re_evaluate: bool,
2264 ) -> Result<Value, String> {
2265 let _lock = self.eval_lock.lock().unwrap();
2267
2268 if let Some(data_str) = data {
2270 let old_data = self.eval_data.clone_data_without(&["$params"]);
2272
2273 let data_value = json_parser::parse_json_str(data_str)?;
2274 let context_value = if let Some(ctx) = context {
2275 json_parser::parse_json_str(ctx)?
2276 } else {
2277 Value::Object(serde_json::Map::new())
2278 };
2279 self.eval_data.replace_data_and_context(data_value.clone(), context_value);
2280
2281 let data_paths: Vec<String> = changed_paths
2285 .iter()
2286 .map(|path| {
2287 let schema_ptr = path_utils::dot_notation_to_schema_pointer(path);
2290
2291 let normalized = schema_ptr.trim_start_matches('#')
2293 .replace("/properties/", "/");
2294
2295 if normalized.starts_with('/') {
2297 normalized
2298 } else {
2299 format!("/{}", normalized)
2300 }
2301 })
2302 .collect();
2303 self.purge_cache_for_changed_data_with_comparison(&data_paths, &old_data, &data_value);
2304 }
2305
2306 let mut result = Vec::new();
2307 let mut processed = IndexSet::new();
2308
2309 let mut to_process: Vec<(String, bool)> = changed_paths
2312 .iter()
2313 .map(|path| (path_utils::dot_notation_to_schema_pointer(path), false))
2314 .collect(); while let Some((current_path, is_transitive)) = to_process.pop() {
2318 if processed.contains(¤t_path) {
2319 continue;
2320 }
2321 processed.insert(current_path.clone());
2322
2323 let current_data_path = path_utils::normalize_to_json_pointer(¤t_path)
2325 .replace("/properties/", "/")
2326 .trim_start_matches('#')
2327 .to_string();
2328 let mut current_value = self.eval_data.data().pointer(¤t_data_path)
2329 .cloned()
2330 .unwrap_or(Value::Null);
2331
2332 if let Some(dependent_items) = self.dependents_evaluations.get(¤t_path) {
2334 for dep_item in dependent_items {
2335 let ref_path = &dep_item.ref_path;
2336 let pointer_path = path_utils::normalize_to_json_pointer(ref_path);
2337 let data_path = pointer_path.replace("/properties/", "/");
2339
2340 let current_ref_value = self.eval_data.data().pointer(&data_path)
2341 .cloned()
2342 .unwrap_or(Value::Null);
2343
2344 let field = self.evaluated_schema.pointer(&pointer_path).cloned();
2346
2347 let parent_path = if let Some(last_slash) = pointer_path.rfind("/properties") {
2349 &pointer_path[..last_slash]
2350 } else {
2351 "/"
2352 };
2353 let mut parent_field = if parent_path.is_empty() || parent_path == "/" {
2354 self.evaluated_schema.clone()
2355 } else {
2356 self.evaluated_schema.pointer(parent_path).cloned()
2357 .unwrap_or_else(|| Value::Object(serde_json::Map::new()))
2358 };
2359
2360 if let Value::Object(ref mut map) = parent_field {
2362 map.remove("properties");
2363 map.remove("$layout");
2364 }
2365
2366 let mut change_obj = serde_json::Map::new();
2367 change_obj.insert("$ref".to_string(), Value::String(path_utils::pointer_to_dot_notation(&data_path)));
2368 if let Some(f) = field {
2369 change_obj.insert("$field".to_string(), f);
2370 }
2371 change_obj.insert("$parentField".to_string(), parent_field);
2372 change_obj.insert("transitive".to_string(), Value::Bool(is_transitive));
2373
2374 let mut add_transitive = false;
2375 let mut add_deps = false;
2376 if let Some(clear_val) = &dep_item.clear {
2378 let clear_val_clone = clear_val.clone();
2379 let should_clear = Self::evaluate_dependent_value_static(&self.engine, &self.evaluations, &self.eval_data, &clear_val_clone, ¤t_value, ¤t_ref_value)?;
2380 let clear_bool = match should_clear {
2381 Value::Bool(b) => b,
2382 _ => false,
2383 };
2384
2385 if clear_bool {
2386 if data_path == current_data_path {
2388 current_value = Value::Null;
2389 }
2390 self.eval_data.set(&data_path, Value::Null);
2391 change_obj.insert("clear".to_string(), Value::Bool(true));
2392 add_transitive = true;
2393 add_deps = true;
2394 }
2395 }
2396
2397 if let Some(value_val) = &dep_item.value {
2399 let value_val_clone = value_val.clone();
2400 let computed_value = Self::evaluate_dependent_value_static(&self.engine, &self.evaluations, &self.eval_data, &value_val_clone, ¤t_value, ¤t_ref_value)?;
2401 let cleaned_val = clean_float_noise(computed_value.clone());
2402
2403 if cleaned_val != current_ref_value && cleaned_val != Value::Null {
2404 if data_path == current_data_path {
2406 current_value = cleaned_val.clone();
2407 }
2408 self.eval_data.set(&data_path, cleaned_val.clone());
2409 change_obj.insert("value".to_string(), cleaned_val);
2410 add_transitive = true;
2411 add_deps = true;
2412 }
2413 }
2414
2415 if add_deps {
2417 result.push(Value::Object(change_obj));
2418 }
2419
2420 if add_transitive {
2422 to_process.push((ref_path.clone(), true));
2423 }
2424 }
2425 }
2426 }
2427
2428 if re_evaluate {
2432 drop(_lock); self.evaluate_internal(None)?;
2434 }
2435
2436 Ok(Value::Array(result))
2437 }
2438
2439 fn evaluate_dependent_value_static(
2441 engine: &RLogic,
2442 evaluations: &IndexMap<String, LogicId>,
2443 eval_data: &EvalData,
2444 value: &Value,
2445 changed_field_value: &Value,
2446 changed_field_ref_value: &Value
2447 ) -> Result<Value, String> {
2448 match value {
2449 Value::String(eval_key) => {
2451 if let Some(logic_id) = evaluations.get(eval_key) {
2452 let mut internal_context = serde_json::Map::new();
2455 internal_context.insert("$value".to_string(), changed_field_value.clone());
2456 internal_context.insert("$refValue".to_string(), changed_field_ref_value.clone());
2457 let context_value = Value::Object(internal_context);
2458
2459 let result = engine.run_with_context(logic_id, eval_data.data(), &context_value)
2460 .map_err(|e| format!("Failed to evaluate dependent logic '{}': {}", eval_key, e))?;
2461 Ok(result)
2462 } else {
2463 Ok(value.clone())
2465 }
2466 }
2467 Value::Object(map) if map.contains_key("$evaluation") => {
2470 Err("Dependent evaluation contains unparsed $evaluation - schema was not properly parsed".to_string())
2471 }
2472 _ => Ok(value.clone()),
2474 }
2475 }
2476
2477 pub fn validate(
2480 &mut self,
2481 data: &str,
2482 context: Option<&str>,
2483 paths: Option<&[String]>
2484 ) -> Result<ValidationResult, String> {
2485 let _lock = self.eval_lock.lock().unwrap();
2487
2488 let old_data = self.eval_data.clone_data_without(&["$params"]);
2490
2491 let data_value = json_parser::parse_json_str(data)?;
2493 let context_value = if let Some(ctx) = context {
2494 json_parser::parse_json_str(ctx)?
2495 } else {
2496 Value::Object(serde_json::Map::new())
2497 };
2498
2499 self.eval_data.replace_data_and_context(data_value.clone(), context_value);
2501
2502 let changed_data_paths: Vec<String> = if let Some(obj) = data_value.as_object() {
2505 obj.keys().map(|k| format!("/{}", k)).collect()
2506 } else {
2507 Vec::new()
2508 };
2509 self.purge_cache_for_changed_data_with_comparison(&changed_data_paths, &old_data, &data_value);
2510
2511 drop(_lock);
2513
2514 self.evaluate_others(paths);
2519
2520 self.evaluated_schema = self.get_evaluated_schema(false);
2522
2523 let mut errors: IndexMap<String, ValidationError> = IndexMap::new();
2524
2525 for field_path in self.fields_with_rules.iter() {
2528 if let Some(filter_paths) = paths {
2530 if !filter_paths.is_empty() && !filter_paths.iter().any(|p| field_path.starts_with(p.as_str()) || p.starts_with(field_path.as_str())) {
2531 continue;
2532 }
2533 }
2534
2535 self.validate_field(field_path, &data_value, &mut errors);
2536 }
2537
2538 let has_error = !errors.is_empty();
2539
2540 Ok(ValidationResult {
2541 has_error,
2542 errors,
2543 })
2544 }
2545
2546 fn validate_field(
2548 &self,
2549 field_path: &str,
2550 data: &Value,
2551 errors: &mut IndexMap<String, ValidationError>
2552 ) {
2553 if errors.contains_key(field_path) {
2555 return;
2556 }
2557
2558 let schema_path = path_utils::dot_notation_to_schema_pointer(field_path);
2560
2561 let pointer_path = schema_path.trim_start_matches('#');
2563
2564 let field_schema = match self.evaluated_schema.pointer(pointer_path) {
2566 Some(s) => s,
2567 None => {
2568 let alt_path = format!("/properties{}", pointer_path);
2570 match self.evaluated_schema.pointer(&alt_path) {
2571 Some(s) => s,
2572 None => return,
2573 }
2574 }
2575 };
2576
2577 if let Value::Object(schema_map) = field_schema {
2579 if let Some(Value::Object(condition)) = schema_map.get("condition") {
2580 if let Some(Value::Bool(true)) = condition.get("hidden") {
2581 return;
2582 }
2583 }
2584
2585 let rules = match schema_map.get("rules") {
2587 Some(Value::Object(r)) => r,
2588 _ => return,
2589 };
2590
2591 let field_data = self.get_field_data(field_path, data);
2593
2594 for (rule_name, rule_value) in rules {
2596 self.validate_rule(
2597 field_path,
2598 rule_name,
2599 rule_value,
2600 &field_data,
2601 schema_map,
2602 field_schema,
2603 errors
2604 );
2605 }
2606 }
2607 }
2608
2609 fn get_field_data(&self, field_path: &str, data: &Value) -> Value {
2611 let parts: Vec<&str> = field_path.split('.').collect();
2612 let mut current = data;
2613
2614 for part in parts {
2615 match current {
2616 Value::Object(map) => {
2617 current = map.get(part).unwrap_or(&Value::Null);
2618 }
2619 _ => return Value::Null,
2620 }
2621 }
2622
2623 current.clone()
2624 }
2625
2626 fn validate_rule(
2628 &self,
2629 field_path: &str,
2630 rule_name: &str,
2631 rule_value: &Value,
2632 field_data: &Value,
2633 schema_map: &serde_json::Map<String, Value>,
2634 _schema: &Value,
2635 errors: &mut IndexMap<String, ValidationError>
2636 ) {
2637 if errors.contains_key(field_path) {
2639 return;
2640 }
2641
2642 let mut disabled_field = false;
2643 if let Some(Value::Object(condition)) = schema_map.get("condition") {
2645 if let Some(Value::Bool(true)) = condition.get("disabled") {
2646 disabled_field = true;
2647 }
2648 }
2649
2650 let schema_path = path_utils::dot_notation_to_schema_pointer(field_path);
2653 let rule_path = format!("{}/rules/{}", schema_path.trim_start_matches('#'), rule_name);
2654
2655 let evaluated_rule = if let Some(eval_rule) = self.evaluated_schema.pointer(&rule_path) {
2657 eval_rule.clone()
2658 } else {
2659 rule_value.clone()
2660 };
2661
2662 let (rule_active, rule_message, rule_code, rule_data) = match &evaluated_rule {
2664 Value::Object(rule_obj) => {
2665 let active = rule_obj.get("value").unwrap_or(&Value::Bool(false));
2666
2667 let message = match rule_obj.get("message") {
2669 Some(Value::String(s)) => s.clone(),
2670 Some(Value::Object(msg_obj)) if msg_obj.contains_key("value") => {
2671 msg_obj.get("value")
2672 .and_then(|v| v.as_str())
2673 .unwrap_or("Validation failed")
2674 .to_string()
2675 }
2676 Some(msg_val) => msg_val.as_str().unwrap_or("Validation failed").to_string(),
2677 None => "Validation failed".to_string()
2678 };
2679
2680 let code = rule_obj.get("code")
2681 .and_then(|c| c.as_str())
2682 .map(|s| s.to_string());
2683
2684 let data = rule_obj.get("data").map(|d| {
2686 if let Value::Object(data_obj) = d {
2687 let mut cleaned_data = serde_json::Map::new();
2688 for (key, value) in data_obj {
2689 if let Value::Object(val_obj) = value {
2691 if val_obj.len() == 1 && val_obj.contains_key("value") {
2692 cleaned_data.insert(key.clone(), val_obj["value"].clone());
2693 } else {
2694 cleaned_data.insert(key.clone(), value.clone());
2695 }
2696 } else {
2697 cleaned_data.insert(key.clone(), value.clone());
2698 }
2699 }
2700 Value::Object(cleaned_data)
2701 } else {
2702 d.clone()
2703 }
2704 });
2705
2706 (active.clone(), message, code, data)
2707 }
2708 _ => (evaluated_rule.clone(), "Validation failed".to_string(), None, None)
2709 };
2710
2711 let error_code = rule_code.or_else(|| Some(format!("{}.{}", field_path, rule_name)));
2713
2714 let is_empty = matches!(field_data, Value::Null) ||
2715 (field_data.is_string() && field_data.as_str().unwrap_or("").is_empty()) ||
2716 (field_data.is_array() && field_data.as_array().unwrap().is_empty());
2717
2718 match rule_name {
2719 "required" => {
2720 if !disabled_field && rule_active == Value::Bool(true) {
2721 if is_empty {
2722 errors.insert(field_path.to_string(), ValidationError {
2723 rule_type: "required".to_string(),
2724 message: rule_message,
2725 code: error_code.clone(),
2726 pattern: None,
2727 field_value: None,
2728 data: None,
2729 });
2730 }
2731 }
2732 }
2733 "minLength" => {
2734 if !is_empty {
2735 if let Some(min) = rule_active.as_u64() {
2736 let len = match field_data {
2737 Value::String(s) => s.len(),
2738 Value::Array(a) => a.len(),
2739 _ => 0
2740 };
2741 if len < min as usize {
2742 errors.insert(field_path.to_string(), ValidationError {
2743 rule_type: "minLength".to_string(),
2744 message: rule_message,
2745 code: error_code.clone(),
2746 pattern: None,
2747 field_value: None,
2748 data: None,
2749 });
2750 }
2751 }
2752 }
2753 }
2754 "maxLength" => {
2755 if !is_empty {
2756 if let Some(max) = rule_active.as_u64() {
2757 let len = match field_data {
2758 Value::String(s) => s.len(),
2759 Value::Array(a) => a.len(),
2760 _ => 0
2761 };
2762 if len > max as usize {
2763 errors.insert(field_path.to_string(), ValidationError {
2764 rule_type: "maxLength".to_string(),
2765 message: rule_message,
2766 code: error_code.clone(),
2767 pattern: None,
2768 field_value: None,
2769 data: None,
2770 });
2771 }
2772 }
2773 }
2774 }
2775 "minValue" => {
2776 if !is_empty {
2777 if let Some(min) = rule_active.as_f64() {
2778 if let Some(val) = field_data.as_f64() {
2779 if val < min {
2780 errors.insert(field_path.to_string(), ValidationError {
2781 rule_type: "minValue".to_string(),
2782 message: rule_message,
2783 code: error_code.clone(),
2784 pattern: None,
2785 field_value: None,
2786 data: None,
2787 });
2788 }
2789 }
2790 }
2791 }
2792 }
2793 "maxValue" => {
2794 if !is_empty {
2795 if let Some(max) = rule_active.as_f64() {
2796 if let Some(val) = field_data.as_f64() {
2797 if val > max {
2798 errors.insert(field_path.to_string(), ValidationError {
2799 rule_type: "maxValue".to_string(),
2800 message: rule_message,
2801 code: error_code.clone(),
2802 pattern: None,
2803 field_value: None,
2804 data: None,
2805 });
2806 }
2807 }
2808 }
2809 }
2810 }
2811 "pattern" => {
2812 if !is_empty {
2813 if let Some(pattern) = rule_active.as_str() {
2814 if let Some(text) = field_data.as_str() {
2815 if let Ok(regex) = regex::Regex::new(pattern) {
2816 if !regex.is_match(text) {
2817 errors.insert(field_path.to_string(), ValidationError {
2818 rule_type: "pattern".to_string(),
2819 message: rule_message,
2820 code: error_code.clone(),
2821 pattern: Some(pattern.to_string()),
2822 field_value: Some(text.to_string()),
2823 data: None,
2824 });
2825 }
2826 }
2827 }
2828 }
2829 }
2830 }
2831 "evaluation" => {
2832 if let Value::Array(eval_array) = &evaluated_rule {
2835 for (idx, eval_item) in eval_array.iter().enumerate() {
2836 if let Value::Object(eval_obj) = eval_item {
2837 let eval_result = eval_obj.get("value").unwrap_or(&Value::Bool(true));
2839
2840 let is_falsy = match eval_result {
2842 Value::Bool(false) => true,
2843 Value::Null => true,
2844 Value::Number(n) => n.as_f64() == Some(0.0),
2845 Value::String(s) => s.is_empty(),
2846 Value::Array(a) => a.is_empty(),
2847 _ => false,
2848 };
2849
2850 if is_falsy {
2851 let eval_code = eval_obj.get("code")
2852 .and_then(|c| c.as_str())
2853 .map(|s| s.to_string())
2854 .or_else(|| Some(format!("{}.evaluation.{}", field_path, idx)));
2855
2856 let eval_message = eval_obj.get("message")
2857 .and_then(|m| m.as_str())
2858 .unwrap_or("Validation failed")
2859 .to_string();
2860
2861 let eval_data = eval_obj.get("data").cloned();
2862
2863 errors.insert(field_path.to_string(), ValidationError {
2864 rule_type: "evaluation".to_string(),
2865 message: eval_message,
2866 code: eval_code,
2867 pattern: None,
2868 field_value: None,
2869 data: eval_data,
2870 });
2871
2872 break;
2874 }
2875 }
2876 }
2877 }
2878 }
2879 _ => {
2880 if !is_empty {
2884 let is_falsy = match &rule_active {
2886 Value::Bool(false) => true,
2887 Value::Null => true,
2888 Value::Number(n) => n.as_f64() == Some(0.0),
2889 Value::String(s) => s.is_empty(),
2890 Value::Array(a) => a.is_empty(),
2891 _ => false,
2892 };
2893
2894 if is_falsy {
2895 errors.insert(field_path.to_string(), ValidationError {
2896 rule_type: "evaluation".to_string(),
2897 message: rule_message,
2898 code: error_code.clone(),
2899 pattern: None,
2900 field_value: None,
2901 data: rule_data,
2902 });
2903 }
2904 }
2905 }
2906 }
2907 }
2908}
2909
2910#[derive(Debug, Clone, Serialize, Deserialize)]
2912pub struct ValidationError {
2913 #[serde(rename = "type")]
2914 pub rule_type: String,
2915 pub message: String,
2916 #[serde(skip_serializing_if = "Option::is_none")]
2917 pub code: Option<String>,
2918 #[serde(skip_serializing_if = "Option::is_none")]
2919 pub pattern: Option<String>,
2920 #[serde(skip_serializing_if = "Option::is_none")]
2921 pub field_value: Option<String>,
2922 #[serde(skip_serializing_if = "Option::is_none")]
2923 pub data: Option<Value>,
2924}
2925
2926#[derive(Debug, Clone, Serialize, Deserialize)]
2928pub struct ValidationResult {
2929 pub has_error: bool,
2930 pub errors: IndexMap<String, ValidationError>,
2931}
2932