1use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc};
47use serde_json::Value;
48use std::net::IpAddr;
49
50use crate::validate::{is_empty_value, Validate};
51
52fn value_as_string(value: &Value) -> String {
58 match value {
59 Value::String(s) => s.clone(),
60 Value::Number(n) => n.to_string(),
61 Value::Bool(b) => {
62 if *b {
63 "1".to_string()
64 } else {
65 String::new()
66 }
67 }
68 Value::Null => String::new(),
69 _ => String::new(),
70 }
71}
72
73fn value_as_f64(value: &Value) -> Option<f64> {
78 if let Some(n) = value.as_f64() {
79 return Some(n);
80 }
81 if let Value::String(s) = value {
82 return s.parse::<f64>().ok();
83 }
84 None
85}
86
87fn value_loose_equals_str(value: &Value, other: &str) -> bool {
94 if let Some(v_num) = value_as_f64(value) {
96 if let Ok(o_num) = other.parse::<f64>() {
97 return v_num == o_num;
98 }
99 }
100 value_as_string(value) == other
102}
103
104fn value_loose_equals(value: &Value, other: &Value) -> bool {
106 if let (Some(v_num), Some(o_num)) = (value_as_f64(value), value_as_f64(other)) {
108 return v_num == o_num;
109 }
110 value_as_string(value) == value_as_string(other)
112}
113
114fn value_loose_compare(value: &Value, other: &Value) -> Option<std::cmp::Ordering> {
118 if let (Some(v_num), Some(o_num)) = (value_as_f64(value), value_as_f64(other)) {
120 return v_num.partial_cmp(&o_num);
121 }
122 Some(value_as_string(value).cmp(&value_as_string(other)))
124}
125
126fn parse_timestamp(value: &Value) -> Option<i64> {
131 let s = match value {
132 Value::String(s) => s.as_str(),
133 Value::Number(n) => {
134 if let Some(i) = n.as_i64() {
136 return Some(i);
137 }
138 return None;
139 }
140 _ => return None,
141 };
142
143 if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
145 return Some(dt.timestamp());
146 }
147 let formats: &[&str] = &[
149 "%Y-%m-%d %H:%M:%S",
150 "%Y-%m-%d",
151 "%Y/%m/%d %H:%M:%S",
152 "%Y/%m/%d",
153 "%Y-%m-%dT%H:%M:%S",
154 "%Y-%m-%dT%H:%M:%SZ",
155 ];
156 for fmt in formats {
157 if let Ok(dt) = NaiveDateTime::parse_from_str(s, fmt) {
158 return Some(dt.and_utc().timestamp());
159 }
160 if let Ok(d) = NaiveDate::parse_from_str(s, fmt) {
161 return d.and_hms_opt(0, 0, 0).map(|t| t.and_utc().timestamp());
162 }
163 }
164 None
165}
166
167fn php_date_format_to_chrono(php_format: &str) -> String {
171 let mut result = String::new();
172 let mut chars = php_format.chars().peekable();
173 while let Some(c) = chars.next() {
174 match c {
175 'Y' => result.push_str("%Y"),
177 'y' => result.push_str("%y"),
178 'm' => result.push_str("%m"),
180 'n' => result.push_str("%_m"),
181 'd' => result.push_str("%d"),
183 'j' => result.push_str("%_d"),
184 'H' => result.push_str("%H"),
186 'G' => result.push_str("%_H"),
187 'i' => result.push_str("%M"),
189 's' => result.push_str("%S"),
191 'a' | 'A' => result.push_str("%P"),
193 '\\' => {
195 if let Some(next) = chars.next() {
196 result.push(next);
197 }
198 }
199 _ => result.push(c),
200 }
201 }
202 result
203}
204
205pub fn eq(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
220 value_loose_equals_str(value, rule)
221}
222
223pub fn egt(value: &Value, rule: &str, data: &Value, _field: &str) -> bool {
233 let other = Validate::get_data_value(data, rule);
234 matches!(
235 value_loose_compare(value, &other),
236 Some(std::cmp::Ordering::Equal | std::cmp::Ordering::Greater)
237 )
238}
239
240pub fn gt(value: &Value, rule: &str, data: &Value, _field: &str) -> bool {
244 let other = Validate::get_data_value(data, rule);
245 matches!(
246 value_loose_compare(value, &other),
247 Some(std::cmp::Ordering::Greater)
248 )
249}
250
251pub fn elt(value: &Value, rule: &str, data: &Value, _field: &str) -> bool {
255 let other = Validate::get_data_value(data, rule);
256 matches!(
257 value_loose_compare(value, &other),
258 Some(std::cmp::Ordering::Equal | std::cmp::Ordering::Less)
259 )
260}
261
262pub fn lt(value: &Value, rule: &str, data: &Value, _field: &str) -> bool {
266 let other = Validate::get_data_value(data, rule);
267 matches!(
268 value_loose_compare(value, &other),
269 Some(std::cmp::Ordering::Less)
270 )
271}
272
273pub fn confirm(value: &Value, rule: &str, data: &Value, field: &str) -> bool {
282 let confirm_field = if rule.is_empty() {
283 if field.contains("_confirm") {
284 field.split("_confirm").next().unwrap_or("").to_string()
285 } else {
286 format!("{}_confirm", field)
287 }
288 } else {
289 rule.to_string()
290 };
291 let other = Validate::get_data_value(data, &confirm_field);
292 value == &other
294}
295
296pub fn different(value: &Value, rule: &str, data: &Value, _field: &str) -> bool {
304 let other = Validate::get_data_value(data, rule);
305 !value_loose_equals(value, &other)
306}
307
308pub fn in_rule(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
322 let items: Vec<&str> = rule.split(',').collect();
323 for item in items {
324 let item = item.trim();
325 if value_loose_equals_str(value, item) {
326 return true;
327 }
328 }
329 false
330}
331
332pub fn not_in(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
336 !in_rule(value, rule, _data, _field)
337}
338
339pub fn between(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
350 let parts: Vec<&str> = rule.split(',').collect();
351 if parts.len() < 2 {
352 return false;
353 }
354 let min = parts[0].trim();
355 let max = parts[1].trim();
356 let ge_min = value_loose_compare_str(value, min)
358 .map(|o| o != std::cmp::Ordering::Less)
359 .unwrap_or(false);
360 let le_max = value_loose_compare_str(value, max)
361 .map(|o| o != std::cmp::Ordering::Greater)
362 .unwrap_or(false);
363 ge_min && le_max
364}
365
366pub fn not_between(value: &Value, rule: &str, data: &Value, field: &str) -> bool {
370 !between(value, rule, data, field)
371}
372
373fn value_loose_compare_str(value: &Value, other: &str) -> Option<std::cmp::Ordering> {
375 if let Some(v_num) = value_as_f64(value) {
377 if let Ok(o_num) = other.parse::<f64>() {
378 return v_num.partial_cmp(&o_num);
379 }
380 }
381 Some(value_as_string(value).as_str().cmp(other))
383}
384
385fn value_length(value: &Value) -> usize {
395 match value {
396 Value::Array(a) => a.len(),
397 Value::Object(o) => o.len(),
398 Value::String(s) => s.chars().count(),
399 _ => value_as_string(value).chars().count(),
400 }
401}
402
403pub fn length(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
414 let len = value_length(value);
415 if let Some(idx) = rule.find(',') {
416 let min_str = rule[..idx].trim();
417 let max_str = rule[idx + 1..].trim();
418 let min: usize = min_str.parse().unwrap_or(0);
419 let max: usize = max_str.parse().unwrap_or(0);
420 len >= min && len <= max
421 } else {
422 let target: usize = rule.parse().unwrap_or(0);
423 len == target
424 }
425}
426
427pub fn max(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
431 let len = value_length(value);
432 let max: usize = rule.parse().unwrap_or(0);
433 len <= max
434}
435
436pub fn min(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
440 let len = value_length(value);
441 let min: usize = rule.parse().unwrap_or(0);
442 len >= min
443}
444
445pub fn date_format(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
460 let s = match value {
461 Value::String(s) => s.as_str(),
462 _ => return false,
463 };
464 let chrono_fmt = php_date_format_to_chrono(rule);
465 if NaiveDateTime::parse_from_str(s, &chrono_fmt).is_ok() {
467 return true;
468 }
469 if NaiveDate::parse_from_str(s, &chrono_fmt).is_ok() {
471 return true;
472 }
473 false
474}
475
476pub fn after(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
484 let value_ts = parse_timestamp(value);
485 let rule_ts = parse_timestamp(&Value::String(rule.to_string()));
486 match (value_ts, rule_ts) {
487 (Some(v), Some(r)) => v >= r,
488 _ => false,
489 }
490}
491
492pub fn before(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
496 let value_ts = parse_timestamp(value);
497 let rule_ts = parse_timestamp(&Value::String(rule.to_string()));
498 match (value_ts, rule_ts) {
499 (Some(v), Some(r)) => v <= r,
500 _ => false,
501 }
502}
503
504pub fn after_with(value: &Value, rule: &str, data: &Value, _field: &str) -> bool {
515 let other = Validate::get_data_value(data, rule);
516 if other.is_null() {
517 return false;
518 }
519 let value_ts = parse_timestamp(value);
520 let rule_ts = parse_timestamp(&other);
521 match (value_ts, rule_ts) {
522 (Some(v), Some(r)) => v >= r,
523 _ => false,
524 }
525}
526
527pub fn before_with(value: &Value, rule: &str, data: &Value, _field: &str) -> bool {
531 let other = Validate::get_data_value(data, rule);
532 if other.is_null() {
533 return false;
534 }
535 let value_ts = parse_timestamp(value);
536 let rule_ts = parse_timestamp(&other);
537 match (value_ts, rule_ts) {
538 (Some(v), Some(r)) => v <= r,
539 _ => false,
540 }
541}
542
543pub fn expire(_value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
556 let parts: Vec<&str> = rule.split(',').collect();
557 if parts.len() < 2 {
558 return false;
559 }
560 let start_str = parts[0].trim();
561 let end_str = parts[1].trim();
562
563 let start_ts = if let Ok(n) = start_str.parse::<i64>() {
565 Some(n)
566 } else {
567 parse_timestamp(&Value::String(start_str.to_string()))
568 };
569 let end_ts = if let Ok(n) = end_str.parse::<i64>() {
570 Some(n)
571 } else {
572 parse_timestamp(&Value::String(end_str.to_string()))
573 };
574
575 match (start_ts, end_ts) {
576 (Some(s), Some(e)) => {
577 let now = Utc::now().timestamp();
578 now >= s && now <= e
579 }
580 _ => false,
581 }
582}
583
584pub fn require_if(value: &Value, rule: &str, data: &Value, _field: &str) -> bool {
602 let parts: Vec<&str> = rule.split(',').collect();
603 if parts.len() < 2 {
604 return true;
605 }
606 let field_name = parts[0].trim();
607 let expected_val = parts[1].trim();
608
609 let actual = Validate::get_data_value(data, field_name);
610 if value_loose_equals_str(&actual, expected_val) {
611 !is_empty_value(value) || matches!(value, Value::String(s) if s == "0")
613 } else {
614 true
615 }
616}
617
618pub fn require_with(value: &Value, rule: &str, data: &Value, _field: &str) -> bool {
632 let other = Validate::get_data_value(data, rule);
633 if !is_empty_value(&other) {
634 !is_empty_value(value) || matches!(value, Value::String(s) if s == "0")
635 } else {
636 true
637 }
638}
639
640pub fn require_without(value: &Value, rule: &str, data: &Value, _field: &str) -> bool {
644 let other = Validate::get_data_value(data, rule);
645 if is_empty_value(&other) {
646 !is_empty_value(value) || matches!(value, Value::String(s) if s == "0")
647 } else {
648 true
649 }
650}
651
652pub fn ip(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
667 let s = match value {
668 Value::String(s) => s.as_str(),
669 _ => return false,
670 };
671 let parsed: Result<IpAddr, _> = s.parse();
672 match parsed {
673 Ok(IpAddr::V4(_)) => rule != "ipv6", Ok(IpAddr::V6(_)) => rule == "ipv6",
675 Err(_) => false,
676 }
677}
678
679pub fn allow_ip(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
683 let s = match value {
684 Value::String(s) => s.as_str(),
685 _ => return false,
686 };
687 let allowed: Vec<&str> = rule.split(',').map(|x| x.trim()).collect();
688 allowed.contains(&s)
689}
690
691pub fn deny_ip(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
695 !allow_ip(value, rule, _data, _field)
696}
697
698pub fn active_url(value: &Value, _rule: &str, _data: &Value, _field: &str) -> bool {
720 let s = match value {
721 Value::String(s) => s.as_str(),
722 _ => return false,
723 };
724 if s.is_empty() {
726 return false;
727 }
728 use std::net::ToSocketAddrs;
731 let target = format!("{}:80", s);
732 target.to_socket_addrs().is_ok()
733}
734
735#[cfg(test)]
740mod tests {
741 use super::*;
742 use serde_json::json;
743
744 #[test]
749 fn test_eq_numeric() {
750 assert!(eq(&json!(1), "1", &Value::Null, ""));
751 assert!(eq(&json!("1"), "1", &Value::Null, ""));
752 assert!(eq(&json!(1.5), "1.5", &Value::Null, ""));
753 assert!(!eq(&json!(2), "1", &Value::Null, ""));
754 }
755
756 #[test]
757 fn test_eq_string() {
758 assert!(eq(&json!("hello"), "hello", &Value::Null, ""));
759 assert!(!eq(&json!("hello"), "world", &Value::Null, ""));
760 }
761
762 #[test]
763 fn test_egt_field_comparison() {
764 let data = json!({"min_val": 10});
765 assert!(egt(&json!(15), "min_val", &data, ""));
766 assert!(egt(&json!(10), "min_val", &data, ""));
767 assert!(!egt(&json!(5), "min_val", &data, ""));
768 }
769
770 #[test]
771 fn test_gt_field_comparison() {
772 let data = json!({"min_val": 10});
773 assert!(gt(&json!(15), "min_val", &data, ""));
774 assert!(!gt(&json!(10), "min_val", &data, ""));
775 assert!(!gt(&json!(5), "min_val", &data, ""));
776 }
777
778 #[test]
779 fn test_elt_field_comparison() {
780 let data = json!({"max_val": 100});
781 assert!(elt(&json!(50), "max_val", &data, ""));
782 assert!(elt(&json!(100), "max_val", &data, ""));
783 assert!(!elt(&json!(150), "max_val", &data, ""));
784 }
785
786 #[test]
787 fn test_lt_field_comparison() {
788 let data = json!({"max_val": 100});
789 assert!(lt(&json!(50), "max_val", &data, ""));
790 assert!(!lt(&json!(100), "max_val", &data, ""));
791 assert!(!lt(&json!(150), "max_val", &data, ""));
792 }
793
794 #[test]
795 fn test_confirm_explicit_field() {
796 let data = json!({"password": "abc123", "password_confirm": "abc123"});
797 assert!(confirm(
798 &json!("abc123"),
799 "password_confirm",
800 &data,
801 "password"
802 ));
803 assert!(!confirm(
804 &json!("wrong"),
805 "password_confirm",
806 &data,
807 "password"
808 ));
809 }
810
811 #[test]
812 fn test_confirm_auto_field_inference() {
813 let data = json!({"password": "abc123", "password_confirm": "abc123"});
815 assert!(confirm(&json!("abc123"), "", &data, "password"));
816 assert!(!confirm(&json!("wrong"), "", &data, "password"));
817 }
818
819 #[test]
820 fn test_confirm_auto_field_strips_suffix() {
821 let data = json!({"password": "abc123"});
823 assert!(confirm(&json!("abc123"), "", &data, "password_confirm"));
824 }
825
826 #[test]
827 fn test_different_loose_comparison() {
828 let data = json!({"other": "abc"});
829 assert!(different(&json!("xyz"), "other", &data, ""));
830 assert!(!different(&json!("abc"), "other", &data, ""));
831 let data2 = json!({"other": "1"});
833 assert!(!different(&json!(1), "other", &data2, ""));
834 }
835
836 #[test]
841 fn test_in_rule() {
842 assert!(in_rule(&json!(1), "1,2,3", &Value::Null, ""));
843 assert!(in_rule(&json!("1"), "1,2,3", &Value::Null, ""));
844 assert!(in_rule(
845 &json!("active"),
846 "active,inactive",
847 &Value::Null,
848 ""
849 ));
850 assert!(!in_rule(&json!(4), "1,2,3", &Value::Null, ""));
851 assert!(!in_rule(&json!("xyz"), "active,inactive", &Value::Null, ""));
852 }
853
854 #[test]
855 fn test_not_in() {
856 assert!(!not_in(&json!(1), "1,2,3", &Value::Null, ""));
857 assert!(not_in(&json!(4), "1,2,3", &Value::Null, ""));
858 }
859
860 #[test]
861 fn test_between_numeric() {
862 assert!(between(&json!(5), "1,10", &Value::Null, ""));
863 assert!(between(&json!(1), "1,10", &Value::Null, ""));
864 assert!(between(&json!(10), "1,10", &Value::Null, ""));
865 assert!(!between(&json!(0), "1,10", &Value::Null, ""));
866 assert!(!between(&json!(11), "1,10", &Value::Null, ""));
867 }
868
869 #[test]
870 fn test_between_string_numeric() {
871 assert!(between(&json!("5"), "1,10", &Value::Null, ""));
873 }
874
875 #[test]
876 fn test_not_between() {
877 assert!(!not_between(&json!(5), "1,10", &Value::Null, ""));
878 assert!(not_between(&json!(11), "1,10", &Value::Null, ""));
879 }
880
881 #[test]
882 fn test_between_invalid_format() {
883 assert!(!between(&json!(5), "1", &Value::Null, "")); }
885
886 #[test]
891 fn test_length_exact() {
892 assert!(length(&json!("abc"), "3", &Value::Null, ""));
893 assert!(!length(&json!("abc"), "5", &Value::Null, ""));
894 }
895
896 #[test]
897 fn test_length_range() {
898 assert!(length(&json!("abc"), "1,5", &Value::Null, ""));
899 assert!(length(&json!("abcde"), "1,5", &Value::Null, ""));
900 assert!(!length(&json!("abcdef"), "1,5", &Value::Null, ""));
901 }
902
903 #[test]
904 fn test_length_unicode() {
905 assert!(length(&json!("中文"), "2", &Value::Null, ""));
907 assert!(!length(&json!("中文"), "4", &Value::Null, "")); }
909
910 #[test]
911 fn test_length_array() {
912 assert!(length(&json!([1, 2, 3]), "3", &Value::Null, ""));
913 assert!(!length(&json!([1, 2, 3]), "2", &Value::Null, ""));
914 }
915
916 #[test]
917 fn test_max_length() {
918 assert!(max(&json!("abc"), "5", &Value::Null, ""));
919 assert!(max(&json!("abcde"), "5", &Value::Null, ""));
920 assert!(!max(&json!("abcdef"), "5", &Value::Null, ""));
921 }
922
923 #[test]
924 fn test_min_length() {
925 assert!(min(&json!("abc"), "3", &Value::Null, ""));
926 assert!(!min(&json!("ab"), "3", &Value::Null, ""));
927 }
928
929 #[test]
934 fn test_date_format_y_m_d() {
935 assert!(date_format(&json!("2024-01-15"), "Y-m-d", &Value::Null, ""));
936 assert!(!date_format(
937 &json!("2024/01/15"),
938 "Y-m-d",
939 &Value::Null,
940 ""
941 ));
942 }
943
944 #[test]
945 fn test_date_format_full() {
946 assert!(date_format(
947 &json!("2024-01-15 12:30:45"),
948 "Y-m-d H:i:s",
949 &Value::Null,
950 ""
951 ));
952 }
953
954 #[test]
955 fn test_after_date() {
956 assert!(after(&json!("2024-01-02"), "2024-01-01", &Value::Null, ""));
957 assert!(after(&json!("2024-01-01"), "2024-01-01", &Value::Null, ""));
958 assert!(!after(&json!("2023-12-31"), "2024-01-01", &Value::Null, ""));
959 }
960
961 #[test]
962 fn test_before_date() {
963 assert!(before(&json!("2023-12-31"), "2024-01-01", &Value::Null, ""));
964 assert!(before(&json!("2024-01-01"), "2024-01-01", &Value::Null, ""));
965 assert!(!before(
966 &json!("2024-01-02"),
967 "2024-01-01",
968 &Value::Null,
969 ""
970 ));
971 }
972
973 #[test]
974 fn test_after_with_field() {
975 let data = json!({"start_date": "2024-01-01"});
976 assert!(after_with(&json!("2024-01-02"), "start_date", &data, ""));
977 assert!(!after_with(&json!("2023-12-31"), "start_date", &data, ""));
978 }
979
980 #[test]
981 fn test_before_with_field() {
982 let data = json!({"end_date": "2024-12-31"});
983 assert!(before_with(&json!("2024-06-15"), "end_date", &data, ""));
984 assert!(!before_with(&json!("2025-01-01"), "end_date", &data, ""));
985 }
986
987 #[test]
988 fn test_after_with_null_field() {
989 let data = json!({});
991 assert!(!after_with(&json!("2024-01-02"), "missing", &data, ""));
992 }
993
994 #[test]
995 fn test_expire_with_timestamps() {
996 let now = Utc::now().timestamp();
998 let past_start = now - 7200; let past_end = now - 3600; let rule = format!("{},{}", past_start, past_end);
1001 assert!(!expire(&Value::Null, &rule, &Value::Null, ""));
1002
1003 let future_start = now - 60;
1005 let future_end = now + 60;
1006 let rule = format!("{},{}", future_start, future_end);
1007 assert!(expire(&Value::Null, &rule, &Value::Null, ""));
1008 }
1009
1010 #[test]
1011 fn test_expire_with_date_strings() {
1012 let rule = "2020-01-01,2030-12-31";
1014 assert!(expire(&Value::Null, rule, &Value::Null, ""));
1015
1016 let rule = "2010-01-01,2015-12-31";
1017 assert!(!expire(&Value::Null, rule, &Value::Null, ""));
1018 }
1019
1020 #[test]
1025 fn test_require_if_condition_met() {
1026 let data = json!({"type": "login"});
1028 assert!(require_if(&json!("alice"), "type,login", &data, ""));
1029 assert!(!require_if(&json!(""), "type,login", &data, ""));
1031 assert!(require_if(&json!("0"), "type,login", &data, ""));
1033 }
1034
1035 #[test]
1036 fn test_require_if_condition_not_met() {
1037 let data = json!({"type": "register"});
1038 assert!(require_if(&json!(""), "type,login", &data, ""));
1040 }
1041
1042 #[test]
1043 fn test_require_with_other_has_value() {
1044 let data = json!({"other_field": "some_value"});
1045 assert!(require_with(&json!("value"), "other_field", &data, ""));
1046 assert!(!require_with(&json!(""), "other_field", &data, ""));
1047 }
1048
1049 #[test]
1050 fn test_require_with_other_empty() {
1051 let data = json!({"other_field": ""});
1052 assert!(require_with(&json!(""), "other_field", &data, ""));
1054
1055 let data2 = json!({});
1056 assert!(require_with(&json!(""), "missing", &data2, ""));
1057 }
1058
1059 #[test]
1060 fn test_require_without_other_empty() {
1061 let data = json!({"other_field": ""});
1062 assert!(require_without(&json!("value"), "other_field", &data, ""));
1064 assert!(!require_without(&json!(""), "other_field", &data, ""));
1065 }
1066
1067 #[test]
1068 fn test_require_without_other_has_value() {
1069 let data = json!({"other_field": "some_value"});
1070 assert!(require_without(&json!(""), "other_field", &data, ""));
1072 }
1073
1074 #[test]
1079 fn test_ip_v4() {
1080 assert!(ip(&json!("127.0.0.1"), "ipv4", &Value::Null, ""));
1081 assert!(ip(&json!("192.168.1.1"), "ipv4", &Value::Null, ""));
1082 assert!(ip(&json!("127.0.0.1"), "", &Value::Null, "")); assert!(!ip(&json!("::1"), "ipv4", &Value::Null, ""));
1084 assert!(!ip(&json!("999.999.999.999"), "ipv4", &Value::Null, ""));
1085 }
1086
1087 #[test]
1088 fn test_ip_v6() {
1089 assert!(ip(&json!("::1"), "ipv6", &Value::Null, ""));
1090 assert!(ip(&json!("2001:db8::1"), "ipv6", &Value::Null, ""));
1091 assert!(!ip(&json!("127.0.0.1"), "ipv6", &Value::Null, ""));
1092 }
1093
1094 #[test]
1095 fn test_allow_ip() {
1096 assert!(allow_ip(
1097 &json!("127.0.0.1"),
1098 "127.0.0.1,192.168.1.1",
1099 &Value::Null,
1100 ""
1101 ));
1102 assert!(!allow_ip(
1103 &json!("10.0.0.1"),
1104 "127.0.0.1,192.168.1.1",
1105 &Value::Null,
1106 ""
1107 ));
1108 }
1109
1110 #[test]
1111 fn test_deny_ip() {
1112 assert!(!deny_ip(
1113 &json!("127.0.0.1"),
1114 "127.0.0.1,192.168.1.1",
1115 &Value::Null,
1116 ""
1117 ));
1118 assert!(deny_ip(
1119 &json!("10.0.0.1"),
1120 "127.0.0.1,192.168.1.1",
1121 &Value::Null,
1122 ""
1123 ));
1124 }
1125
1126 #[test]
1131 fn test_active_url_valid_domain() {
1132 assert!(active_url(&json!("localhost"), "", &Value::Null, ""));
1134 }
1135
1136 #[test]
1137 fn test_active_url_invalid() {
1138 assert!(!active_url(
1139 &json!("not.a.valid.domain.example.invalid"),
1140 "",
1141 &Value::Null,
1142 ""
1143 ));
1144 assert!(!active_url(&json!(""), "", &Value::Null, ""));
1145 assert!(!active_url(&json!(123), "", &Value::Null, ""));
1146 }
1147
1148 #[test]
1153 fn test_value_loose_equals_str_numeric() {
1154 assert!(value_loose_equals_str(&json!(1), "1"));
1155 assert!(value_loose_equals_str(&json!(1.0), "1"));
1156 assert!(value_loose_equals_str(&json!("1"), "1"));
1157 assert!(!value_loose_equals_str(&json!(2), "1"));
1158 }
1159
1160 #[test]
1161 fn test_value_loose_equals_str_string() {
1162 assert!(value_loose_equals_str(&json!("hello"), "hello"));
1163 assert!(!value_loose_equals_str(&json!("hello"), "world"));
1164 }
1165
1166 #[test]
1167 fn test_value_loose_compare_numeric() {
1168 use std::cmp::Ordering;
1169 assert_eq!(
1170 value_loose_compare(&json!(5), &json!(3)),
1171 Some(Ordering::Greater)
1172 );
1173 assert_eq!(
1174 value_loose_compare(&json!(3), &json!(5)),
1175 Some(Ordering::Less)
1176 );
1177 assert_eq!(
1178 value_loose_compare(&json!(5), &json!(5)),
1179 Some(Ordering::Equal)
1180 );
1181 }
1182
1183 #[test]
1184 fn test_value_length_string() {
1185 assert_eq!(value_length(&json!("abc")), 3);
1186 assert_eq!(value_length(&json!("中文")), 2); }
1188
1189 #[test]
1190 fn test_value_length_array() {
1191 assert_eq!(value_length(&json!([1, 2, 3])), 3);
1192 assert_eq!(value_length(&json!([])), 0);
1193 }
1194
1195 #[test]
1196 fn test_parse_timestamp_iso() {
1197 let ts = parse_timestamp(&json!("2024-01-01 12:00:00"));
1198 assert!(ts.is_some());
1199 }
1200
1201 #[test]
1202 fn test_parse_timestamp_numeric() {
1203 let ts = parse_timestamp(&json!(1700000000));
1204 assert_eq!(ts, Some(1700000000));
1205 }
1206
1207 #[test]
1208 fn test_php_date_format_to_chrono_simple() {
1209 assert_eq!(php_date_format_to_chrono("Y-m-d"), "%Y-%m-%d");
1210 assert_eq!(
1211 php_date_format_to_chrono("Y/m/d H:i:s"),
1212 "%Y/%m/%d %H:%M:%S"
1213 );
1214 }
1215}