1use super::JSONEval;
2use crate::jsoneval::cancellation::CancellationToken;
3use crate::jsoneval::json_parser;
4use crate::jsoneval::path_utils;
5use crate::jsoneval::types::{ValidationError, ValidationResult};
6
7use crate::time_block;
8
9use indexmap::IndexMap;
10use serde_json::Value;
11
12impl JSONEval {
13 pub(crate) fn invalidate_validation_cache(&self) {
15 let mut cache = match self.validation_cache.write() {
16 Ok(c) => c,
17 Err(poisoned) => poisoned.into_inner(),
18 };
19 cache.clear();
20 }
21
22 pub fn validate(
24 &mut self,
25 data: &str,
26 context: Option<&str>,
27 paths: Option<&[String]>,
28 token: Option<&CancellationToken>,
29 validate_readonly: Option<bool>,
30 include_subforms: Option<bool>,
31 ) -> Result<ValidationResult, String> {
32 let validate_ro = validate_readonly.unwrap_or(false);
33 let inc_subforms = include_subforms.unwrap_or(false);
34 if let Some(t) = token {
35 if t.is_cancelled() {
36 return Err("Cancelled".to_string());
37 }
38 }
39
40 if paths.is_none() || paths.is_some_and(|p| p.is_empty()) {
43 if let Ok(cache) = self.validation_cache.read() {
44 if let Some(cached) =
45 cache.get_cached_full_result(data, context, validate_ro, inc_subforms)
46 {
47 return Ok(cached);
48 }
49 }
50 }
51
52 time_block!("validate() [total]", {
53 let _lock = self.eval_lock.lock().unwrap();
55 let _static_guard = self
56 .engine
57 .bind_static_arrays_scope(std::sync::Arc::clone(&self.static_arrays));
58
59 let (data_value, context_value) = time_block!(" parse data & context", {
61 let d = json_parser::parse_json_str(data)?;
62 let c = if let Some(ctx) = context {
63 json_parser::parse_json_str(ctx)?
64 } else {
65 Value::Object(serde_json::Map::new())
66 };
67 Ok::<_, String>((d, c))
68 })?;
69
70 self.context = context_value.clone();
72
73 time_block!(" replace_data_and_context", {
75 self.eval_data
76 .replace_data_and_context(data_value.clone(), context_value.clone());
77 });
78
79 drop(_lock);
81
82 time_block!(" evaluate_others", {
85 self.evaluate_others(paths, token);
86 });
87
88 time_block!(" ensure_layout_resolved", {
89 self.ensure_layout_resolved();
90 });
91
92 let mut errors: IndexMap<String, ValidationError> = IndexMap::new();
93
94 let layout_state = self.layout_state.read().unwrap();
95 let layout_hidden_refs = &layout_state.layout_hidden_refs;
96 let layout_disabled_refs = &layout_state.layout_disabled_refs;
97 let mut hidden_cache =
98 std::collections::HashMap::with_capacity(self.fields_with_rules.len());
99 let mut readonly_cache =
100 std::collections::HashMap::with_capacity(self.fields_with_rules.len());
101
102 time_block!(" fields_with_rules loop", {
105 for field_path in self.fields_with_rules.iter() {
106 if let Some(filter_paths) = paths {
108 if !filter_paths.is_empty()
109 && !filter_paths.iter().any(|p| {
110 field_path.starts_with(p.as_str())
111 || p.starts_with(field_path.as_str())
112 })
113 {
114 continue;
115 }
116 }
117
118 if let Some(t) = token {
119 if t.is_cancelled() {
120 return Err("Cancelled".to_string());
121 }
122 }
123
124 self.validate_field_cached(
125 field_path,
126 &data_value,
127 layout_hidden_refs,
128 layout_disabled_refs,
129 &mut hidden_cache,
130 &mut readonly_cache,
131 validate_ro,
132 &mut errors,
133 );
134 }
135 });
136
137 drop(layout_state);
138
139 if inc_subforms {
140 let initial_eval_cache = self.eval_cache.clone();
141 let initial_static_arrays = std::sync::Arc::clone(&self.static_arrays);
142 let subform_keys: Vec<String> = self.subforms.keys().cloned().collect();
143
144 for subform_path in subform_keys {
145 if let Some(t) = token {
146 if t.is_cancelled() {
147 return Err("Cancelled".to_string());
148 }
149 }
150
151 let data_ptr = path_utils::schema_path_to_data_pointer(&subform_path);
152 let subform_dot_path = data_ptr.trim_start_matches('/').replace('/', ".");
153
154 let schema_pointer = if subform_path.starts_with("#/") {
155 &subform_path[1..]
156 } else if subform_path.starts_with('#') {
157 &subform_path[1..]
158 } else {
159 &subform_path
160 };
161
162 let original_field_key = subform_path
163 .split('/')
164 .filter(|seg| !seg.is_empty() && *seg != "properties")
165 .last()
166 .unwrap_or(&subform_path)
167 .to_string();
168
169 let root_key = path_utils::get_value_by_pointer(&self.schema, schema_pointer)
170 .and_then(|node| node.get("itemsRootKey"))
171 .and_then(|v| v.as_str())
172 .unwrap_or(&original_field_key)
173 .to_string();
174
175 let item_count = self
176 .eval_data
177 .data()
178 .pointer(&data_ptr)
179 .and_then(Value::as_array)
180 .map(|a| a.len())
181 .unwrap_or(0);
182
183 if item_count == 0 {
184 continue;
185 }
186
187 for idx in 0..item_count {
188 if let Some(t) = token {
189 if t.is_cancelled() {
190 return Err("Cancelled".to_string());
191 }
192 }
193
194 let item_prefix = format!("{}.{}.", subform_dot_path, idx);
195 let item_path_exact = format!("{}.{}", subform_dot_path, idx);
196
197 let sub_paths: Option<Vec<String>> = if let Some(filter_paths) = paths {
198 if filter_paths.is_empty() {
199 None
200 } else {
201 let applies = filter_paths.iter().any(|p| {
202 p == &item_path_exact
203 || p == &subform_dot_path
204 || p.starts_with(&item_prefix)
205 || subform_dot_path.starts_with(p.as_str())
206 });
207 if !applies {
208 continue;
209 }
210
211 let has_whole_match = filter_paths.iter().any(|p| {
212 p == &item_path_exact
213 || p == &subform_dot_path
214 || subform_dot_path.starts_with(p.as_str())
215 });
216
217 if has_whole_match {
218 None
219 } else {
220 let mapped: Vec<String> = filter_paths
221 .iter()
222 .filter_map(|p| {
223 p.strip_prefix(&item_prefix)
224 .map(|sub| format!("{}.{}", root_key, sub))
225 })
226 .collect();
227
228 if mapped.is_empty() {
229 continue;
230 }
231 Some(mapped)
232 }
233 }
234 } else {
235 None
236 };
237
238 let sub_paths_ref = sub_paths.as_deref();
239
240 let sub_result = self.with_item_cache_swap(
241 &subform_path,
242 idx,
243 data_value.clone(),
244 context_value.clone(),
245 false,
246 |sf| {
247 sf.validate_pre_set(
248 None,
249 sub_paths_ref,
250 token,
251 validate_readonly,
252 true,
253 )
254 },
255 )?;
256
257 let root_prefix = format!("{}.", root_key);
258 for (field_name, mut error) in sub_result.errors {
259 let sub_field =
260 if let Some(stripped) = field_name.strip_prefix(&root_prefix) {
261 stripped
262 } else if field_name == root_key {
263 ""
264 } else {
265 &field_name
266 };
267
268 let parent_path = if sub_field.is_empty() {
269 format!("{}.{}", subform_dot_path, idx)
270 } else {
271 format!("{}.{}.{}", subform_dot_path, idx, sub_field)
272 };
273
274 if let Some(ref c) = error.code {
275 if c == &format!("{}.{}", field_name, error.rule_type)
276 || c == &format!("{}.{}", sub_field, error.rule_type)
277 {
278 error.code =
279 Some(format!("{}.{}", parent_path, error.rule_type));
280 }
281 }
282
283 errors.insert(parent_path, error);
284 }
285 }
286 }
287
288 for subform in self.subforms.values_mut() {
290 subform.eval_data = crate::jsoneval::eval_data::EvalData::new(Value::Null);
291 subform.evaluated_schema = (*subform.schema).clone();
292 subform.eval_cache.clear();
293 subform.static_arrays = std::sync::Arc::new(IndexMap::new());
294 subform
295 .engine
296 .set_static_arrays(std::sync::Arc::clone(&subform.static_arrays));
297 subform.invalidate_layout_cache();
298 subform.invalidate_validation_cache();
299 }
300 self.eval_cache = initial_eval_cache;
301 self.static_arrays = initial_static_arrays;
302 self.engine
303 .set_static_arrays(std::sync::Arc::clone(&self.static_arrays));
304 self.invalidate_layout_cache();
305 }
306
307 let has_error = !errors.is_empty();
308 let result = ValidationResult { has_error, errors };
309
310 if paths.is_none() || paths.is_some_and(|p| p.is_empty()) {
311 if let Ok(mut cache) = self.validation_cache.write() {
312 cache.save_full_result(
313 data.to_string(),
314 context.map(|s| s.to_string()),
315 validate_ro,
316 inc_subforms,
317 result.clone(),
318 );
319 }
320 } else if let Ok(mut cache) = self.validation_cache.write() {
321 cache.invalidate_full_result();
322 }
323
324 Ok(result)
325 })
326 }
327
328 pub(crate) fn validate_pre_set(
333 &mut self,
334 data_value: Option<&Value>,
335 paths: Option<&[String]>,
336 token: Option<&CancellationToken>,
337 validate_readonly: Option<bool>,
338 re_evaluate_others: bool,
339 ) -> Result<crate::ValidationResult, String> {
340 let validate_ro = validate_readonly.unwrap_or(false);
341 let _static_guard = self
342 .engine
343 .bind_static_arrays_scope(std::sync::Arc::clone(&self.static_arrays));
344 if re_evaluate_others {
345 self.evaluate_others(paths, token);
347 }
348
349 self.ensure_layout_resolved();
350
351 let mut errors: IndexMap<String, ValidationError> = IndexMap::new();
352
353 let layout_state = self.layout_state.read().unwrap();
354 let layout_hidden_refs = &layout_state.layout_hidden_refs;
355 let layout_disabled_refs = &layout_state.layout_disabled_refs;
356 let mut hidden_cache =
357 std::collections::HashMap::with_capacity(self.fields_with_rules.len());
358 let mut readonly_cache =
359 std::collections::HashMap::with_capacity(self.fields_with_rules.len());
360
361 let target_data = if let Some(dv) = data_value {
362 if dv.is_object()
363 && self.fields_with_rules.iter().any(|f| {
364 let root = f.split('.').next().unwrap_or(f);
365 dv.get(root).is_some()
366 })
367 {
368 dv
369 } else {
370 self.eval_data.data()
371 }
372 } else {
373 self.eval_data.data()
374 };
375
376 for field_path in self.fields_with_rules.iter() {
377 if let Some(filter_paths) = paths {
378 if !filter_paths.is_empty()
379 && !filter_paths.iter().any(|p| {
380 field_path.starts_with(p.as_str()) || p.starts_with(field_path.as_str())
381 })
382 {
383 continue;
384 }
385 }
386 if let Some(t) = token {
387 if t.is_cancelled() {
388 return Err("Cancelled".to_string());
389 }
390 }
391 self.validate_field_cached(
392 field_path,
393 target_data,
394 layout_hidden_refs,
395 layout_disabled_refs,
396 &mut hidden_cache,
397 &mut readonly_cache,
398 validate_ro,
399 &mut errors,
400 );
401 }
402
403 drop(layout_state);
404
405 let has_error = !errors.is_empty();
406 Ok(crate::ValidationResult { has_error, errors })
407 }
408
409 #[allow(dead_code)]
411 pub(crate) fn validate_field(
412 &self,
413 field_path: &str,
414 data: &Value,
415 validate_readonly: bool,
416 errors: &mut IndexMap<String, ValidationError>,
417 ) {
418 let layout_state = self.layout_state.read().unwrap();
419 let mut hidden_cache = std::collections::HashMap::new();
420 let mut readonly_cache = std::collections::HashMap::new();
421 self.validate_field_cached(
422 field_path,
423 data,
424 &layout_state.layout_hidden_refs,
425 &layout_state.layout_disabled_refs,
426 &mut hidden_cache,
427 &mut readonly_cache,
428 validate_readonly,
429 errors,
430 );
431 }
432
433 #[allow(clippy::too_many_arguments)]
435 pub(crate) fn validate_field_cached(
436 &self,
437 field_path: &str,
438 data: &Value,
439 layout_hidden_refs: &indexmap::IndexSet<String>,
440 layout_disabled_refs: &indexmap::IndexSet<String>,
441 hidden_cache: &mut std::collections::HashMap<String, bool>,
442 readonly_cache: &mut std::collections::HashMap<String, bool>,
443 validate_readonly: bool,
444 errors: &mut IndexMap<String, ValidationError>,
445 ) {
446 if errors.contains_key(field_path) {
448 return;
449 }
450
451 let schema_path = path_utils::dot_notation_to_schema_pointer(field_path);
453 let pointer_path = schema_path.trim_start_matches('#');
454
455 let (field_schema, resolved_path) = match self.evaluated_schema.pointer(pointer_path) {
457 Some(s) => (s, pointer_path.to_string()),
458 None => {
459 let alt_path = format!("/properties{}", pointer_path);
460 match self.evaluated_schema.pointer(&alt_path) {
461 Some(s) => (s, alt_path),
462 None => return,
463 }
464 }
465 };
466
467 let is_hidden =
469 self.is_effective_hidden_with_cache(&resolved_path, layout_hidden_refs, hidden_cache);
470 if is_hidden {
471 if let Ok(mut cache) = self.validation_cache.write() {
472 cache.update_field(
473 field_path.to_string(),
474 Value::Null,
475 true,
476 validate_readonly,
477 Value::Null,
478 None,
479 );
480 }
481 return;
482 }
483
484 if !validate_readonly {
486 let is_readonly = self.is_effective_readonly_with_cache(
487 &resolved_path,
488 layout_disabled_refs,
489 readonly_cache,
490 );
491 if is_readonly {
492 if let Ok(mut cache) = self.validation_cache.write() {
493 cache.update_field(
494 field_path.to_string(),
495 Value::Null,
496 false,
497 false,
498 Value::Null,
499 None,
500 );
501 }
502 return;
503 }
504 }
505
506 if let Value::Object(schema_map) = field_schema {
507 let rules_val = match schema_map.get("rules") {
509 Some(r @ Value::Object(_)) => r,
510 _ => return,
511 };
512 let rules = rules_val.as_object().unwrap();
513
514 let field_data = self.get_field_data(field_path, data);
516
517 let cached_lookup = if let Ok(cache) = self.validation_cache.read() {
519 cache.check_field_cache(
520 field_path,
521 &field_data,
522 false,
523 validate_readonly,
524 rules_val,
525 )
526 } else {
527 None
528 };
529
530 if let Some(cached_error) = cached_lookup {
531 if let Some(err) = cached_error {
532 errors.insert(field_path.to_string(), err);
533 }
534 return;
535 }
536
537 let mut field_error: Option<ValidationError> = None;
539 time_block!(" validate_rules loop", {
540 for (rule_name, rule_value) in rules {
541 self.validate_rule(
542 field_path,
543 rule_name,
544 rule_value,
545 &field_data,
546 schema_map,
547 field_schema,
548 errors,
549 );
550 if let Some(err) = errors.get(field_path) {
551 field_error = Some(err.clone());
552 break;
553 }
554 }
555 });
556
557 if let Ok(mut cache) = self.validation_cache.write() {
558 cache.update_field(
559 field_path.to_string(),
560 field_data,
561 false,
562 validate_readonly,
563 rules_val.clone(),
564 field_error,
565 );
566 }
567 }
568 }
569
570 pub(crate) fn get_field_data(&self, field_path: &str, data: &Value) -> Value {
572 let mut current = data;
573
574 for part in field_path.split('.') {
575 match current {
576 Value::Object(map) => {
577 current = map.get(part).unwrap_or(&Value::Null);
578 }
579 _ => return Value::Null,
580 }
581 }
582
583 current.clone()
584 }
585
586 #[allow(clippy::too_many_arguments)]
588 pub(crate) fn validate_rule(
589 &self,
590 field_path: &str,
591 rule_name: &str,
592 rule_value: &Value,
593 field_data: &Value,
594 schema_map: &serde_json::Map<String, Value>,
595 _schema: &Value,
596 errors: &mut IndexMap<String, ValidationError>,
597 ) {
598 if errors.contains_key(field_path) {
600 return;
601 }
602
603 let schema_type = schema_map
604 .get("type")
605 .and_then(|t| t.as_str())
606 .unwrap_or("");
607
608 let evaluated_rule = rule_value;
610
611 let (rule_active, rule_message, rule_code, rule_data) = match evaluated_rule {
614 Value::Object(rule_obj) => {
615 let active = rule_obj.get("value").unwrap_or(&Value::Bool(false));
616
617 let message = match rule_obj.get("message") {
619 Some(Value::String(s)) => s.clone(),
620 Some(Value::Object(msg_obj)) if msg_obj.contains_key("value") => msg_obj
621 .get("value")
622 .and_then(|v| v.as_str())
623 .unwrap_or("Validation failed")
624 .to_string(),
625 Some(msg_val) => msg_val.as_str().unwrap_or("Validation failed").to_string(),
626 None => "Validation failed".to_string(),
627 };
628
629 let code = rule_obj
630 .get("code")
631 .and_then(|c| c.as_str())
632 .map(|s| s.to_string());
633
634 let data = rule_obj.get("data").map(|d| {
636 if let Value::Object(data_obj) = d {
637 let mut cleaned_data = serde_json::Map::new();
638 for (key, value) in data_obj {
639 if let Value::Object(val_obj) = value {
641 if val_obj.len() == 1 && val_obj.contains_key("value") {
642 cleaned_data.insert(key.clone(), val_obj["value"].clone());
643 } else {
644 cleaned_data.insert(key.clone(), value.clone());
645 }
646 } else {
647 cleaned_data.insert(key.clone(), value.clone());
648 }
649 }
650 Value::Object(cleaned_data)
651 } else {
652 d.clone()
653 }
654 });
655
656 (active.clone(), message, code, data)
657 }
658 _ => (
659 evaluated_rule.clone(),
660 "Validation failed".to_string(),
661 None,
662 None,
663 ),
664 };
665
666 let error_code = rule_code.or_else(|| Some(format!("{}.{}", field_path, rule_name)));
668
669 let is_empty = matches!(field_data, Value::Null)
670 || (field_data.is_string() && field_data.as_str().unwrap_or("").is_empty())
671 || (field_data.is_array() && field_data.as_array().unwrap().is_empty());
672
673 match rule_name {
674 "required" => {
675 if rule_active == Value::Bool(true) {
676 if is_empty {
677 errors.insert(
678 field_path.to_string(),
679 ValidationError {
680 rule_type: "required".to_string(),
681 message: rule_message,
682 code: error_code,
683 pattern: None,
684 field_value: None,
685 data: build_error_data(
686 rule_name,
687 &rule_active,
688 rule_data,
689 schema_map,
690 ),
691 },
692 );
693 }
694 }
695 }
696 "minLength" | "maxLength" | "minValue" | "maxValue" => {
697 if rule_value_fails(rule_name, &rule_active, field_data, is_empty, schema_type) {
698 errors.insert(
699 field_path.to_string(),
700 ValidationError {
701 rule_type: rule_name.to_string(),
702 message: rule_message,
703 code: error_code,
704 pattern: None,
705 field_value: None,
706 data: build_error_data(rule_name, &rule_active, rule_data, schema_map),
707 },
708 );
709 }
710 }
711
712 "pattern" => {
713 if !is_empty {
714 if let Some(pattern) = rule_active.as_str() {
715 if let Some(text) = field_data.as_str() {
716 let cached_regex = if let Ok(cache) = self.regex_cache.read() {
717 cache.get(pattern).cloned()
718 } else {
719 None
720 };
721 let regex = match cached_regex {
722 Some(r) => r,
723 None => {
724 let mut cache = self.regex_cache.write().unwrap();
725 cache
726 .entry(pattern.to_string())
727 .or_insert_with(|| {
728 regex::Regex::new(pattern).unwrap_or_else(|_| {
729 regex::Regex::new("(?:)").unwrap()
730 })
731 })
732 .clone()
733 }
734 };
735 if !regex.is_match(text) {
736 errors.insert(
737 field_path.to_string(),
738 ValidationError {
739 rule_type: "pattern".to_string(),
740 message: rule_message,
741 code: error_code,
742 pattern: Some(pattern.to_string()),
743 field_value: Some(text.to_string()),
744 data: build_error_data(
745 rule_name,
746 &rule_active,
747 rule_data,
748 schema_map,
749 ),
750 },
751 );
752 }
753 }
754 }
755 }
756 }
757 "evaluation" => {
758 if let Value::Array(eval_array) = evaluated_rule {
761 for (idx, eval_item) in eval_array.iter().enumerate() {
762 if let Value::Object(eval_obj) = eval_item {
763 let eval_result = eval_obj.get("value").unwrap_or(&Value::Bool(true));
765
766 let is_falsy = match eval_result {
768 Value::Bool(false) => true,
769 Value::Null => true,
770 Value::Number(n) => n.as_f64() == Some(0.0),
771 Value::String(s) => s.is_empty(),
772 Value::Array(a) => a.is_empty(),
773 _ => false,
774 };
775
776 if is_falsy {
777 let eval_code = eval_obj
778 .get("code")
779 .and_then(|c| c.as_str())
780 .map(|s| s.to_string())
781 .or_else(|| Some(format!("{}.evaluation.{}", field_path, idx)));
782
783 let eval_message = eval_obj
784 .get("message")
785 .and_then(|m| m.as_str())
786 .unwrap_or("Validation failed")
787 .to_string();
788
789 let eval_data = eval_obj.get("data").cloned();
790
791 errors.insert(
792 field_path.to_string(),
793 ValidationError {
794 rule_type: "evaluation".to_string(),
795 message: eval_message,
796 code: eval_code,
797 pattern: None,
798 field_value: None,
799 data: build_error_data(
800 "evaluation",
801 &Value::Null,
802 eval_data,
803 schema_map,
804 ),
805 },
806 );
807
808 break;
810 }
811 }
812 }
813 }
814 }
815 _ => {
816 if rule_value_fails(rule_name, &rule_active, field_data, is_empty, schema_type) {
817 errors.insert(
818 field_path.to_string(),
819 ValidationError {
820 rule_type: "evaluation".to_string(),
821 message: rule_message,
822 code: error_code,
823 pattern: None,
824 field_value: None,
825 data: build_error_data(rule_name, &rule_active, rule_data, schema_map),
826 },
827 );
828 }
829 }
830 }
831 }
832
833 pub(crate) fn dep_fails_schema_rules(
840 &self,
841 field_path: &str,
842 field_data: &Value,
843 scope_data: &Value,
844 ) -> bool {
845 let schema_pointer = path_utils::dot_notation_to_schema_pointer(field_path);
846 let pointer = schema_pointer.trim_start_matches('#');
847
848 let field_schema = match self.schema.pointer(pointer) {
849 Some(s) => s,
850 None => {
851 let alt_pointer = format!("/properties{}", pointer);
852 match self.schema.pointer(&alt_pointer) {
853 Some(s) => s,
854 None => return false,
855 }
856 }
857 };
858
859 let schema_map = match field_schema.as_object() {
860 Some(m) => m,
861 None => return false,
862 };
863
864 let rules = match schema_map.get("rules") {
865 Some(Value::Object(r)) => r,
866 _ => return false,
867 };
868
869 let schema_type = schema_map
870 .get("type")
871 .and_then(|t| t.as_str())
872 .unwrap_or("");
873
874 let is_empty = matches!(field_data, Value::Null)
875 || field_data.as_str().map_or(false, |s| s.is_empty())
876 || field_data.as_array().map_or(false, |a| a.is_empty());
877
878 for (rule_name, rule_value) in rules {
879 let rule_eval_key = format!("#{}/rules/{}", pointer, rule_name);
883 let rule_active: Value = if let Some(logic_id) = self.evaluations.get(&rule_eval_key) {
884 let empty_ctx = Value::Object(serde_json::Map::new());
885 self.engine
886 .run_with_context(logic_id, scope_data, &empty_ctx)
887 .unwrap_or(Value::Null)
888 } else {
889 match rule_value {
890 Value::Object(obj) => obj.get("value").cloned().unwrap_or(Value::Null),
891 other => other.clone(),
892 }
893 };
894
895 if rule_value_fails(rule_name, &rule_active, field_data, is_empty, schema_type) {
896 return true;
897 }
898 }
899
900 false
901 }
902}
903
904fn rule_value_fails(
913 rule_name: &str,
914 rule_active: &Value,
915 field_data: &Value,
916 is_empty: bool,
917 schema_type: &str,
918) -> bool {
919 let coerce_num = |v: &Value| -> Option<f64> {
920 if let Some(n) = v.as_f64() {
921 return Some(n);
922 }
923 if matches!(schema_type, "number" | "integer") {
924 if let Some(s) = v.as_str() {
925 return s.trim().parse::<f64>().ok();
926 }
927 }
928 None
929 };
930
931 match rule_name {
932 "required" => is_empty && matches!(rule_active, Value::Bool(true)),
933 "minLength" => {
934 if is_empty {
935 false
936 } else if let Some(min) = rule_active.as_u64() {
937 let len = match field_data {
938 Value::String(s) => s.len(),
939 Value::Array(a) => a.len(),
940 _ => 0,
941 };
942 len < min as usize
943 } else {
944 false
945 }
946 }
947 "maxLength" => {
948 if is_empty {
949 false
950 } else if let Some(max) = rule_active.as_u64() {
951 let len = match field_data {
952 Value::String(s) => s.len(),
953 Value::Array(a) => a.len(),
954 _ => 0,
955 };
956 len > max as usize
957 } else {
958 false
959 }
960 }
961 "minValue" => {
962 if is_empty {
963 false
964 } else if let Some(min) = rule_active.as_f64() {
965 coerce_num(field_data).map_or(false, |v| v < min)
966 } else {
967 false
968 }
969 }
970 "maxValue" => {
971 if is_empty {
972 false
973 } else if let Some(max) = rule_active.as_f64() {
974 coerce_num(field_data).map_or(false, |v| v > max)
975 } else {
976 false
977 }
978 }
979 "pattern" | "evaluation" => false,
981 _ => {
982 if is_empty {
984 false
985 } else {
986 matches!(rule_active, Value::Bool(false) | Value::Null)
987 || rule_active.as_f64() == Some(0.0)
988 || rule_active.as_str().map_or(false, |s| s.is_empty())
989 || rule_active.as_array().map_or(false, |a| a.is_empty())
990 }
991 }
992 }
993}
994
995fn build_error_data(
1002 rule_name: &str,
1003 rule_active: &Value,
1004 rule_data: Option<Value>,
1005 schema_map: &serde_json::Map<String, Value>,
1006) -> Option<Value> {
1007 let mut data_map = serde_json::Map::new();
1008
1009 if let Some(title) = schema_map.get("title") {
1011 if !title.is_null() {
1012 data_map.insert("title".to_string(), title.clone());
1013 }
1014 }
1015 if let Some(description) = schema_map.get("description") {
1016 if !description.is_null() {
1017 data_map.insert("description".to_string(), description.clone());
1018 }
1019 }
1020
1021 let get_rule_val = |r: &Value| -> Option<Value> {
1022 match r {
1023 Value::Object(obj) => obj.get("value").cloned(),
1024 other if !other.is_null() => Some(other.clone()),
1025 _ => None,
1026 }
1027 };
1028
1029 match rule_name {
1031 "required" => {
1032 data_map.insert("required".to_string(), Value::Bool(true));
1033 }
1034 "minValue" => {
1035 data_map.insert("minValue".to_string(), rule_active.clone());
1036 }
1037 "maxValue" => {
1038 data_map.insert("maxValue".to_string(), rule_active.clone());
1039 }
1040 "minLength" => {
1041 data_map.insert("minLength".to_string(), rule_active.clone());
1042 }
1043 "maxLength" => {
1044 data_map.insert("maxLength".to_string(), rule_active.clone());
1045 }
1046 _ => {}
1047 }
1048
1049 if matches!(
1051 rule_name,
1052 "minValue" | "maxValue" | "minLength" | "maxLength"
1053 ) {
1054 if let Some(Value::Object(rules)) = schema_map.get("rules") {
1055 match rule_name {
1056 "minValue" => {
1057 if let Some(max_rule) = rules.get("maxValue") {
1058 if let Some(val) = get_rule_val(max_rule) {
1059 data_map.insert("maxValue".to_string(), val);
1060 }
1061 }
1062 }
1063 "maxValue" => {
1064 if let Some(min_rule) = rules.get("minValue") {
1065 if let Some(val) = get_rule_val(min_rule) {
1066 data_map.insert("minValue".to_string(), val);
1067 }
1068 }
1069 }
1070 "minLength" => {
1071 if let Some(max_rule) = rules.get("maxLength") {
1072 if let Some(val) = get_rule_val(max_rule) {
1073 data_map.insert("maxLength".to_string(), val);
1074 }
1075 }
1076 }
1077 "maxLength" => {
1078 if let Some(min_rule) = rules.get("minLength") {
1079 if let Some(val) = get_rule_val(min_rule) {
1080 data_map.insert("minLength".to_string(), val);
1081 }
1082 }
1083 }
1084 _ => {}
1085 }
1086 }
1087 }
1088
1089 if let Some(rule_d) = rule_data {
1091 if let Value::Object(schema_data) = rule_d {
1092 for (k, v) in schema_data {
1093 data_map.insert(k, v);
1094 }
1095 } else if data_map.is_empty() {
1096 return Some(rule_d);
1097 }
1098 }
1099
1100 if data_map.is_empty() {
1101 None
1102 } else {
1103 Some(Value::Object(data_map))
1104 }
1105}