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 =
56 self.engine.bind_static_arrays_scope(std::sync::Arc::clone(&self.static_arrays));
57
58 let (data_value, context_value) = time_block!(" parse data & context", {
60 let d = json_parser::parse_json_str(data)?;
61 let c = if let Some(ctx) = context {
62 json_parser::parse_json_str(ctx)?
63 } else {
64 Value::Object(serde_json::Map::new())
65 };
66 Ok::<_, String>((d, c))
67 })?;
68
69 self.context = context_value.clone();
71
72 time_block!(" replace_data_and_context", {
74 self.eval_data
75 .replace_data_and_context(data_value.clone(), context_value.clone());
76 });
77
78 drop(_lock);
80
81 time_block!(" evaluate_others", {
84 self.evaluate_others(paths, token);
85 });
86
87 time_block!(" ensure_layout_resolved", {
88 self.ensure_layout_resolved();
89 });
90
91 let mut errors: IndexMap<String, ValidationError> = IndexMap::new();
92
93 let layout_state = self.layout_state.read().unwrap();
94 let layout_hidden_refs = &layout_state.layout_hidden_refs;
95 let layout_disabled_refs = &layout_state.layout_disabled_refs;
96 let mut hidden_cache =
97 std::collections::HashMap::with_capacity(self.fields_with_rules.len());
98 let mut readonly_cache =
99 std::collections::HashMap::with_capacity(self.fields_with_rules.len());
100
101 time_block!(" fields_with_rules loop", {
104 for field_path in self.fields_with_rules.iter() {
105 if let Some(filter_paths) = paths {
107 if !filter_paths.is_empty()
108 && !filter_paths.iter().any(|p| {
109 field_path.starts_with(p.as_str())
110 || p.starts_with(field_path.as_str())
111 })
112 {
113 continue;
114 }
115 }
116
117 if let Some(t) = token {
118 if t.is_cancelled() {
119 return Err("Cancelled".to_string());
120 }
121 }
122
123 self.validate_field_cached(
124 field_path,
125 &data_value,
126 layout_hidden_refs,
127 layout_disabled_refs,
128 &mut hidden_cache,
129 &mut readonly_cache,
130 validate_ro,
131 &mut errors,
132 );
133 }
134 });
135
136 drop(layout_state);
137
138 if inc_subforms {
139 let subform_keys: Vec<String> = self.subforms.keys().cloned().collect();
140
141 for subform_path in subform_keys {
142 if let Some(t) = token {
143 if t.is_cancelled() {
144 return Err("Cancelled".to_string());
145 }
146 }
147
148 let data_ptr = path_utils::schema_path_to_data_pointer(&subform_path);
149 let subform_dot_path = data_ptr.trim_start_matches('/').replace('/', ".");
150
151 let schema_pointer = if subform_path.starts_with("#/") {
152 &subform_path[1..]
153 } else if subform_path.starts_with('#') {
154 &subform_path[1..]
155 } else {
156 &subform_path
157 };
158
159 let original_field_key = subform_path
160 .split('/')
161 .filter(|seg| !seg.is_empty() && *seg != "properties")
162 .last()
163 .unwrap_or(&subform_path)
164 .to_string();
165
166 let root_key = path_utils::get_value_by_pointer(&self.schema, schema_pointer)
167 .and_then(|node| node.get("itemsRootKey"))
168 .and_then(|v| v.as_str())
169 .unwrap_or(&original_field_key)
170 .to_string();
171
172 let item_count = self
173 .eval_data
174 .data()
175 .pointer(&data_ptr)
176 .and_then(Value::as_array)
177 .map(|a| a.len())
178 .unwrap_or(0);
179
180 if item_count == 0 {
181 continue;
182 }
183
184 for idx in 0..item_count {
185 if let Some(t) = token {
186 if t.is_cancelled() {
187 return Err("Cancelled".to_string());
188 }
189 }
190
191 let item_prefix = format!("{}.{}.", subform_dot_path, idx);
192 let item_path_exact = format!("{}.{}", subform_dot_path, idx);
193
194 let sub_paths: Option<Vec<String>> = if let Some(filter_paths) = paths {
195 if filter_paths.is_empty() {
196 None
197 } else {
198 let applies = filter_paths.iter().any(|p| {
199 p == &item_path_exact
200 || p == &subform_dot_path
201 || p.starts_with(&item_prefix)
202 || subform_dot_path.starts_with(p.as_str())
203 });
204 if !applies {
205 continue;
206 }
207
208 let has_whole_match = filter_paths.iter().any(|p| {
209 p == &item_path_exact
210 || p == &subform_dot_path
211 || subform_dot_path.starts_with(p.as_str())
212 });
213
214 if has_whole_match {
215 None
216 } else {
217 let mapped: Vec<String> = filter_paths
218 .iter()
219 .filter_map(|p| {
220 p.strip_prefix(&item_prefix)
221 .map(|sub| format!("{}.{}", root_key, sub))
222 })
223 .collect();
224
225 if mapped.is_empty() {
226 continue;
227 }
228 Some(mapped)
229 }
230 }
231 } else {
232 None
233 };
234
235 let sub_paths_ref = sub_paths.as_deref();
236
237 let sub_result = self.with_item_cache_swap(
238 &subform_path,
239 idx,
240 data_value.clone(),
241 context_value.clone(),
242 |sf| {
243 sf.evaluate_internal_pre_diffed(sub_paths_ref, token)?;
244 let sub_data = sf.eval_data.snapshot_data_clone();
245 sf.validate_pre_set(
246 sub_data,
247 sub_paths_ref,
248 token,
249 validate_readonly,
250 )
251 },
252 )?;
253
254 let root_prefix = format!("{}.", root_key);
255 for (field_name, mut error) in sub_result.errors {
256 let sub_field =
257 if let Some(stripped) = field_name.strip_prefix(&root_prefix) {
258 stripped
259 } else if field_name == root_key {
260 ""
261 } else {
262 &field_name
263 };
264
265 let parent_path = if sub_field.is_empty() {
266 format!("{}.{}", subform_dot_path, idx)
267 } else {
268 format!("{}.{}.{}", subform_dot_path, idx, sub_field)
269 };
270
271 if let Some(ref c) = error.code {
272 if c == &format!("{}.{}", field_name, error.rule_type)
273 || c == &format!("{}.{}", sub_field, error.rule_type)
274 {
275 error.code =
276 Some(format!("{}.{}", parent_path, error.rule_type));
277 }
278 }
279
280 errors.insert(parent_path, error);
281 }
282 }
283 }
284 }
285
286 let has_error = !errors.is_empty();
287 let result = ValidationResult { has_error, errors };
288
289 if paths.is_none() || paths.is_some_and(|p| p.is_empty()) {
290 if let Ok(mut cache) = self.validation_cache.write() {
291 cache.save_full_result(
292 data.to_string(),
293 context.map(|s| s.to_string()),
294 validate_ro,
295 inc_subforms,
296 result.clone(),
297 );
298 }
299 } else if let Ok(mut cache) = self.validation_cache.write() {
300 cache.invalidate_full_result();
301 }
302
303 Ok(result)
304 })
305 }
306
307 pub(crate) fn validate_pre_set(
312 &mut self,
313 data_value: Value,
314 paths: Option<&[String]>,
315 token: Option<&CancellationToken>,
316 validate_readonly: Option<bool>,
317 ) -> Result<crate::ValidationResult, String> {
318 let validate_ro = validate_readonly.unwrap_or(false);
319 let _static_guard =
320 self.engine.bind_static_arrays_scope(std::sync::Arc::clone(&self.static_arrays));
321 self.evaluate_others(paths, token);
323
324 self.ensure_layout_resolved();
325
326 let mut errors: IndexMap<String, ValidationError> = IndexMap::new();
327
328 let layout_state = self.layout_state.read().unwrap();
329 let layout_hidden_refs = &layout_state.layout_hidden_refs;
330 let layout_disabled_refs = &layout_state.layout_disabled_refs;
331 let mut hidden_cache =
332 std::collections::HashMap::with_capacity(self.fields_with_rules.len());
333 let mut readonly_cache =
334 std::collections::HashMap::with_capacity(self.fields_with_rules.len());
335
336 let eval_data_val = self.eval_data.snapshot_data_clone();
337 let target_data = if data_value.is_object()
338 && self.fields_with_rules.iter().any(|f| {
339 let root = f.split('.').next().unwrap_or(f);
340 data_value.get(root).is_some()
341 })
342 {
343 &data_value
344 } else {
345 &eval_data_val
346 };
347
348 for field_path in self.fields_with_rules.iter() {
349 if let Some(filter_paths) = paths {
350 if !filter_paths.is_empty()
351 && !filter_paths.iter().any(|p| {
352 field_path.starts_with(p.as_str()) || p.starts_with(field_path.as_str())
353 })
354 {
355 continue;
356 }
357 }
358 if let Some(t) = token {
359 if t.is_cancelled() {
360 return Err("Cancelled".to_string());
361 }
362 }
363 self.validate_field_cached(
364 field_path,
365 target_data,
366 layout_hidden_refs,
367 layout_disabled_refs,
368 &mut hidden_cache,
369 &mut readonly_cache,
370 validate_ro,
371 &mut errors,
372 );
373 }
374
375 drop(layout_state);
376
377 let has_error = !errors.is_empty();
378 Ok(crate::ValidationResult { has_error, errors })
379 }
380
381 #[allow(dead_code)]
383 pub(crate) fn validate_field(
384 &self,
385 field_path: &str,
386 data: &Value,
387 validate_readonly: bool,
388 errors: &mut IndexMap<String, ValidationError>,
389 ) {
390 let layout_state = self.layout_state.read().unwrap();
391 let mut hidden_cache = std::collections::HashMap::new();
392 let mut readonly_cache = std::collections::HashMap::new();
393 self.validate_field_cached(
394 field_path,
395 data,
396 &layout_state.layout_hidden_refs,
397 &layout_state.layout_disabled_refs,
398 &mut hidden_cache,
399 &mut readonly_cache,
400 validate_readonly,
401 errors,
402 );
403 }
404
405 #[allow(clippy::too_many_arguments)]
407 pub(crate) fn validate_field_cached(
408 &self,
409 field_path: &str,
410 data: &Value,
411 layout_hidden_refs: &indexmap::IndexSet<String>,
412 layout_disabled_refs: &indexmap::IndexSet<String>,
413 hidden_cache: &mut std::collections::HashMap<String, bool>,
414 readonly_cache: &mut std::collections::HashMap<String, bool>,
415 validate_readonly: bool,
416 errors: &mut IndexMap<String, ValidationError>,
417 ) {
418 if errors.contains_key(field_path) {
420 return;
421 }
422
423 let schema_path = path_utils::dot_notation_to_schema_pointer(field_path);
425 let pointer_path = schema_path.trim_start_matches('#');
426
427 let (field_schema, resolved_path) = match self.evaluated_schema.pointer(pointer_path) {
429 Some(s) => (s, pointer_path.to_string()),
430 None => {
431 let alt_path = format!("/properties{}", pointer_path);
432 match self.evaluated_schema.pointer(&alt_path) {
433 Some(s) => (s, alt_path),
434 None => return,
435 }
436 }
437 };
438
439 let is_hidden = self.is_effective_hidden_with_cache(
441 &resolved_path,
442 layout_hidden_refs,
443 hidden_cache,
444 );
445 if is_hidden {
446 if let Ok(mut cache) = self.validation_cache.write() {
447 cache.update_field(
448 field_path.to_string(),
449 Value::Null,
450 true,
451 validate_readonly,
452 Value::Null,
453 None,
454 );
455 }
456 return;
457 }
458
459 if !validate_readonly {
461 let is_readonly = self.is_effective_readonly_with_cache(
462 &resolved_path,
463 layout_disabled_refs,
464 readonly_cache,
465 );
466 if is_readonly {
467 if let Ok(mut cache) = self.validation_cache.write() {
468 cache.update_field(
469 field_path.to_string(),
470 Value::Null,
471 false,
472 false,
473 Value::Null,
474 None,
475 );
476 }
477 return;
478 }
479 }
480
481 if let Value::Object(schema_map) = field_schema {
482 let rules_val = match schema_map.get("rules") {
484 Some(r @ Value::Object(_)) => r,
485 _ => return,
486 };
487 let rules = rules_val.as_object().unwrap();
488
489 let field_data = self.get_field_data(field_path, data);
491
492 let cached_lookup = if let Ok(cache) = self.validation_cache.read() {
494 cache.check_field_cache(field_path, &field_data, false, validate_readonly, rules_val)
495 } else {
496 None
497 };
498
499 if let Some(cached_error) = cached_lookup {
500 if let Some(err) = cached_error {
501 errors.insert(field_path.to_string(), err);
502 }
503 return;
504 }
505
506 let mut field_error: Option<ValidationError> = None;
508 time_block!(" validate_rules loop", {
509 for (rule_name, rule_value) in rules {
510 self.validate_rule(
511 field_path,
512 rule_name,
513 rule_value,
514 &field_data,
515 schema_map,
516 field_schema,
517 errors,
518 );
519 if let Some(err) = errors.get(field_path) {
520 field_error = Some(err.clone());
521 break;
522 }
523 }
524 });
525
526 if let Ok(mut cache) = self.validation_cache.write() {
527 cache.update_field(
528 field_path.to_string(),
529 field_data,
530 false,
531 validate_readonly,
532 rules_val.clone(),
533 field_error,
534 );
535 }
536 }
537 }
538
539 pub(crate) fn get_field_data(&self, field_path: &str, data: &Value) -> Value {
541 let mut current = data;
542
543 for part in field_path.split('.') {
544 match current {
545 Value::Object(map) => {
546 current = map.get(part).unwrap_or(&Value::Null);
547 }
548 _ => return Value::Null,
549 }
550 }
551
552 current.clone()
553 }
554
555 #[allow(clippy::too_many_arguments)]
557 pub(crate) fn validate_rule(
558 &self,
559 field_path: &str,
560 rule_name: &str,
561 rule_value: &Value,
562 field_data: &Value,
563 schema_map: &serde_json::Map<String, Value>,
564 _schema: &Value,
565 errors: &mut IndexMap<String, ValidationError>,
566 ) {
567 if errors.contains_key(field_path) {
569 return;
570 }
571
572 let schema_type = schema_map
573 .get("type")
574 .and_then(|t| t.as_str())
575 .unwrap_or("");
576
577 let evaluated_rule = rule_value;
579
580 let (rule_active, rule_message, rule_code, rule_data) = match evaluated_rule {
583 Value::Object(rule_obj) => {
584 let active = rule_obj.get("value").unwrap_or(&Value::Bool(false));
585
586 let message = match rule_obj.get("message") {
588 Some(Value::String(s)) => s.clone(),
589 Some(Value::Object(msg_obj)) if msg_obj.contains_key("value") => msg_obj
590 .get("value")
591 .and_then(|v| v.as_str())
592 .unwrap_or("Validation failed")
593 .to_string(),
594 Some(msg_val) => msg_val.as_str().unwrap_or("Validation failed").to_string(),
595 None => "Validation failed".to_string(),
596 };
597
598 let code = rule_obj
599 .get("code")
600 .and_then(|c| c.as_str())
601 .map(|s| s.to_string());
602
603 let data = rule_obj.get("data").map(|d| {
605 if let Value::Object(data_obj) = d {
606 let mut cleaned_data = serde_json::Map::new();
607 for (key, value) in data_obj {
608 if let Value::Object(val_obj) = value {
610 if val_obj.len() == 1 && val_obj.contains_key("value") {
611 cleaned_data.insert(key.clone(), val_obj["value"].clone());
612 } else {
613 cleaned_data.insert(key.clone(), value.clone());
614 }
615 } else {
616 cleaned_data.insert(key.clone(), value.clone());
617 }
618 }
619 Value::Object(cleaned_data)
620 } else {
621 d.clone()
622 }
623 });
624
625 (active.clone(), message, code, data)
626 }
627 _ => (
628 evaluated_rule.clone(),
629 "Validation failed".to_string(),
630 None,
631 None,
632 ),
633 };
634
635 let error_code = rule_code.or_else(|| Some(format!("{}.{}", field_path, rule_name)));
637
638 let is_empty = matches!(field_data, Value::Null)
639 || (field_data.is_string() && field_data.as_str().unwrap_or("").is_empty())
640 || (field_data.is_array() && field_data.as_array().unwrap().is_empty());
641
642 match rule_name {
643 "required" => {
644 if rule_active == Value::Bool(true) {
645 if is_empty {
646 errors.insert(
647 field_path.to_string(),
648 ValidationError {
649 rule_type: "required".to_string(),
650 message: rule_message,
651 code: error_code,
652 pattern: None,
653 field_value: None,
654 data: None,
655 },
656 );
657 }
658 }
659 }
660 "minLength" | "maxLength" | "minValue" | "maxValue" => {
661 if rule_value_fails(rule_name, &rule_active, field_data, is_empty, schema_type) {
662 errors.insert(
663 field_path.to_string(),
664 ValidationError {
665 rule_type: rule_name.to_string(),
666 message: rule_message,
667 code: error_code,
668 pattern: None,
669 field_value: None,
670 data: None,
671 },
672 );
673 }
674 }
675
676 "pattern" => {
677 if !is_empty {
678 if let Some(pattern) = rule_active.as_str() {
679 if let Some(text) = field_data.as_str() {
680 let cached_regex = if let Ok(cache) = self.regex_cache.read() {
681 cache.get(pattern).cloned()
682 } else {
683 None
684 };
685 let regex = match cached_regex {
686 Some(r) => r,
687 None => {
688 let mut cache = self.regex_cache.write().unwrap();
689 cache.entry(pattern.to_string()).or_insert_with(|| {
690 regex::Regex::new(pattern)
691 .unwrap_or_else(|_| regex::Regex::new("(?:)").unwrap())
692 }).clone()
693 }
694 };
695 if !regex.is_match(text) {
696 errors.insert(
697 field_path.to_string(),
698 ValidationError {
699 rule_type: "pattern".to_string(),
700 message: rule_message,
701 code: error_code,
702 pattern: Some(pattern.to_string()),
703 field_value: Some(text.to_string()),
704 data: None,
705 },
706 );
707 }
708 }
709 }
710 }
711 }
712 "evaluation" => {
713 if let Value::Array(eval_array) = evaluated_rule {
716 for (idx, eval_item) in eval_array.iter().enumerate() {
717 if let Value::Object(eval_obj) = eval_item {
718 let eval_result = eval_obj.get("value").unwrap_or(&Value::Bool(true));
720
721 let is_falsy = match eval_result {
723 Value::Bool(false) => true,
724 Value::Null => true,
725 Value::Number(n) => n.as_f64() == Some(0.0),
726 Value::String(s) => s.is_empty(),
727 Value::Array(a) => a.is_empty(),
728 _ => false,
729 };
730
731 if is_falsy {
732 let eval_code = eval_obj
733 .get("code")
734 .and_then(|c| c.as_str())
735 .map(|s| s.to_string())
736 .or_else(|| Some(format!("{}.evaluation.{}", field_path, idx)));
737
738 let eval_message = eval_obj
739 .get("message")
740 .and_then(|m| m.as_str())
741 .unwrap_or("Validation failed")
742 .to_string();
743
744 let eval_data = eval_obj.get("data").cloned();
745
746 errors.insert(
747 field_path.to_string(),
748 ValidationError {
749 rule_type: "evaluation".to_string(),
750 message: eval_message,
751 code: eval_code,
752 pattern: None,
753 field_value: None,
754 data: eval_data,
755 },
756 );
757
758 break;
760 }
761 }
762 }
763 }
764 }
765 _ => {
766 if rule_value_fails(rule_name, &rule_active, field_data, is_empty, schema_type) {
767 errors.insert(
768 field_path.to_string(),
769 ValidationError {
770 rule_type: "evaluation".to_string(),
771 message: rule_message,
772 code: error_code,
773 pattern: None,
774 field_value: None,
775 data: rule_data,
776 },
777 );
778 }
779 }
780 }
781 }
782
783 pub(crate) fn dep_fails_schema_rules(
790 &self,
791 field_path: &str,
792 field_data: &Value,
793 scope_data: &Value,
794 ) -> bool {
795 let schema_pointer = path_utils::dot_notation_to_schema_pointer(field_path);
796 let pointer = schema_pointer.trim_start_matches('#');
797
798 let field_schema = match self.schema.pointer(pointer) {
799 Some(s) => s,
800 None => {
801 let alt_pointer = format!("/properties{}", pointer);
802 match self.schema.pointer(&alt_pointer) {
803 Some(s) => s,
804 None => return false,
805 }
806 }
807 };
808
809 let schema_map = match field_schema.as_object() {
810 Some(m) => m,
811 None => return false,
812 };
813
814 let rules = match schema_map.get("rules") {
815 Some(Value::Object(r)) => r,
816 _ => return false,
817 };
818
819 let schema_type = schema_map
820 .get("type")
821 .and_then(|t| t.as_str())
822 .unwrap_or("");
823
824 let is_empty = matches!(field_data, Value::Null)
825 || field_data.as_str().map_or(false, |s| s.is_empty())
826 || field_data.as_array().map_or(false, |a| a.is_empty());
827
828 for (rule_name, rule_value) in rules {
829 let rule_eval_key = format!("#{}/rules/{}", pointer, rule_name);
833 let rule_active: Value = if let Some(logic_id) = self.evaluations.get(&rule_eval_key) {
834 let empty_ctx = Value::Object(serde_json::Map::new());
835 self.engine
836 .run_with_context(logic_id, scope_data, &empty_ctx)
837 .unwrap_or(Value::Null)
838 } else {
839 match rule_value {
840 Value::Object(obj) => obj.get("value").cloned().unwrap_or(Value::Null),
841 other => other.clone(),
842 }
843 };
844
845 if rule_value_fails(rule_name, &rule_active, field_data, is_empty, schema_type) {
846 return true;
847 }
848 }
849
850 false
851 }
852}
853
854fn rule_value_fails(
863 rule_name: &str,
864 rule_active: &Value,
865 field_data: &Value,
866 is_empty: bool,
867 schema_type: &str,
868) -> bool {
869 let coerce_num = |v: &Value| -> Option<f64> {
870 if let Some(n) = v.as_f64() {
871 return Some(n);
872 }
873 if matches!(schema_type, "number" | "integer") {
874 if let Some(s) = v.as_str() {
875 return s.trim().parse::<f64>().ok();
876 }
877 }
878 None
879 };
880
881 match rule_name {
882 "required" => is_empty && matches!(rule_active, Value::Bool(true)),
883 "minLength" => {
884 if is_empty {
885 false
886 } else if let Some(min) = rule_active.as_u64() {
887 let len = match field_data {
888 Value::String(s) => s.len(),
889 Value::Array(a) => a.len(),
890 _ => 0,
891 };
892 len < min as usize
893 } else {
894 false
895 }
896 }
897 "maxLength" => {
898 if is_empty {
899 false
900 } else if let Some(max) = rule_active.as_u64() {
901 let len = match field_data {
902 Value::String(s) => s.len(),
903 Value::Array(a) => a.len(),
904 _ => 0,
905 };
906 len > max as usize
907 } else {
908 false
909 }
910 }
911 "minValue" => {
912 if is_empty {
913 false
914 } else if let Some(min) = rule_active.as_f64() {
915 coerce_num(field_data).map_or(false, |v| v < min)
916 } else {
917 false
918 }
919 }
920 "maxValue" => {
921 if is_empty {
922 false
923 } else if let Some(max) = rule_active.as_f64() {
924 coerce_num(field_data).map_or(false, |v| v > max)
925 } else {
926 false
927 }
928 }
929 "pattern" | "evaluation" => false,
931 _ => {
932 if is_empty {
934 false
935 } else {
936 matches!(rule_active, Value::Bool(false) | Value::Null)
937 || rule_active.as_f64() == Some(0.0)
938 || rule_active.as_str().map_or(false, |s| s.is_empty())
939 || rule_active.as_array().map_or(false, |a| a.is_empty())
940 }
941 }
942 }
943}