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