Skip to main content

sz_rust_infra_facade/validate/
rules.rs

1//! 内置验证规则 — 对齐 PHP `think\Validate` 类的内置规则方法
2//!
3//! 本模块实现 PHP `think\Validate` 类中除 `require`/`must`/`is`/`regex` 外的
4//! 所有内置规则方法。
5//!
6//! ## PHP 对齐
7//!
8//! 所有规则函数签名统一为:
9//! ```ignore
10//! fn rule(value: &Value, rule: &str, data: &Value, field: &str) -> bool
11//! ```
12//!
13//! 对齐 PHP `$this->$type($value, $rule, $data, $field, $title)` 调用。
14//!
15//! ## PHP 源码参考
16//!
17//! - `e:\vue\test\鲜视达\server\vendor\topthink\framework\src\think\Validate.php`
18//!   - 第 717-728 行:`confirm`
19//!   - 第 738-741 行:`different`
20//!   - 第 751-754 行:`egt`
21//!   - 第 764-767 行:`gt`
22//!   - 第 777-780 行:`elt`
23//!   - 第 790-793 行:`lt`
24//!   - 第 802-805 行:`eq`
25//!   - 第 926-933 行:`activeUrl`
26//!   - 第 942-949 行:`ip`
27//!   - 第 1109-1113 行:`dateFormat`
28//!   - 第 1200-1209 行:`requireIf`
29//!   - 第 1238-1247 行:`requireWith`
30//!   - 第 1257-1266 行:`requireWithout`
31//!   - 第 1275-1278 行:`in`
32//!   - 第 1287-1290 行:`notIn`
33//!   - 第 1299-1307 行:`between`
34//!   - 第 1316-1324 行:`notBetween`
35//!   - 第 1333-1351 行:`length`
36//!   - 第 1360-1371 行:`max`
37//!   - 第 1380-1391 行:`min`
38//!   - 第 1401-1404 行:`after`
39//!   - 第 1414-1417 行:`before`
40//!   - 第 1427-1431 行:`afterWith`
41//!   - 第 1441-1445 行:`beforeWith`
42//!   - 第 1454-1471 行:`expire`
43//!   - 第 1480-1483 行:`allowIp`
44//!   - 第 1492-1495 行:`denyIp`
45
46use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc};
47use serde_json::Value;
48use std::net::IpAddr;
49
50use crate::validate::{is_empty_value, Validate};
51
52// ============================================================================
53// 内部辅助函数
54// ============================================================================
55
56/// 将 Value 转为字符串(对齐 PHP `(string) $value`)
57fn 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
73/// 将 Value 转为 f64(对齐 PHP 数字字符串转数字)
74///
75/// PHP 松散比较中,数字字符串会被当作数字处理。
76/// 本函数处理 Value::Number 和 Value::String 两种情况。
77fn 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
87/// PHP 松散比较 `==` — 对齐 PHP `==` 运算符
88///
89/// ## PHP 行为
90///
91/// - 数字与数字字符串按数字比较(`1 == "1"` 为 true)
92/// - 其他按字符串比较
93fn value_loose_equals_str(value: &Value, other: &str) -> bool {
94    // 尝试数字比较(PHP 松散比较:数字字符串按数字比较)
95    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    // 字符串比较
101    value_as_string(value) == other
102}
103
104/// PHP 松散比较 `==`(Value 与 Value)
105fn value_loose_equals(value: &Value, other: &Value) -> bool {
106    // 尝试数字比较(PHP 松散比较:数字字符串按数字比较)
107    if let (Some(v_num), Some(o_num)) = (value_as_f64(value), value_as_f64(other)) {
108        return v_num == o_num;
109    }
110    // 字符串比较
111    value_as_string(value) == value_as_string(other)
112}
113
114/// PHP 松散比较 `>=`/`>`/`<=`/`<`(Value 与 Value)
115///
116/// 返回 `Some(Ordering)` 表示可比较,`None` 表示不可比较(视为不满足)
117fn value_loose_compare(value: &Value, other: &Value) -> Option<std::cmp::Ordering> {
118    // 尝试数字比较(PHP 松散比较:数字字符串按数字比较)
119    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    // 字符串比较
123    Some(value_as_string(value).cmp(&value_as_string(other)))
124}
125
126/// 解析时间戳(对齐 PHP `strtotime`)
127///
128/// PHP `strtotime` 解析多种日期格式,返回 Unix 时间戳。
129/// 本函数尝试常见格式解析,返回 Unix 时间戳(秒)。
130fn parse_timestamp(value: &Value) -> Option<i64> {
131    let s = match value {
132        Value::String(s) => s.as_str(),
133        Value::Number(n) => {
134            // 数字直接作为时间戳
135            if let Some(i) = n.as_i64() {
136                return Some(i);
137            }
138            return None;
139        }
140        _ => return None,
141    };
142
143    // 尝试 RFC3339
144    if let Ok(dt) = DateTime::parse_from_rfc3339(s) {
145        return Some(dt.timestamp());
146    }
147    // 尝试常见格式
148    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
167/// 将 PHP 日期格式字符串转换为 chrono 格式字符串
168///
169/// PHP 格式说明符参考:https://www.php.net/manual/en/datetime.format.php
170fn 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            // 年
176            'Y' => result.push_str("%Y"),
177            'y' => result.push_str("%y"),
178            // 月
179            'm' => result.push_str("%m"),
180            'n' => result.push_str("%_m"),
181            // 日
182            'd' => result.push_str("%d"),
183            'j' => result.push_str("%_d"),
184            // 时
185            'H' => result.push_str("%H"),
186            'G' => result.push_str("%_H"),
187            // 分
188            'i' => result.push_str("%M"),
189            // 秒
190            's' => result.push_str("%S"),
191            // AM/PM
192            'a' | 'A' => result.push_str("%P"),
193            // 转义字符
194            '\\' => {
195                if let Some(next) = chars.next() {
196                    result.push(next);
197                }
198            }
199            _ => result.push(c),
200        }
201    }
202    result
203}
204
205// ============================================================================
206// 比较类规则
207// ============================================================================
208
209/// 验证是否等于某个值 — 对齐 PHP `eq`
210///
211/// 对齐 PHP `Validate.php` 第 802-805 行
212///
213/// ## PHP 行为
214///
215/// `return $value == $rule;`(松散比较)
216///
217/// - 数字与数字字符串按数字比较(`1 == "1"` 为 true)
218/// - 其他按字符串比较
219pub fn eq(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
220    value_loose_equals_str(value, rule)
221}
222
223/// 验证是否大于等于某个字段的值 — 对齐 PHP `egt`
224///
225/// 对齐 PHP `Validate.php` 第 751-754 行
226///
227/// ## PHP 行为
228///
229/// `return $value >= $this->getDataValue($data, $rule);`
230///
231/// **注意**:PHP `egt` 的 `$rule` 是字段名,比较 `value` 与 `data[rule]` 的值。
232pub 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
240/// 验证是否大于某个字段的值 — 对齐 PHP `gt`
241///
242/// 对齐 PHP `Validate.php` 第 764-767 行
243pub 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
251/// 验证是否小于等于某个字段的值 — 对齐 PHP `elt`
252///
253/// 对齐 PHP `Validate.php` 第 777-780 行
254pub 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
262/// 验证是否小于某个字段的值 — 对齐 PHP `lt`
263///
264/// 对齐 PHP `Validate.php` 第 790-793 行
265pub 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
273/// 验证是否和某个字段的值是否一致 — 对齐 PHP `confirm`
274///
275/// 对齐 PHP `Validate.php` 第 717-728 行
276///
277/// ## PHP 行为
278///
279/// - 如果 `rule` 为空,根据 `field` 推断确认字段名(`field_confirm` 或 `field + '_confirm'`)
280/// - 比较 `value` 与 `data[rule]` 的值(严格比较 `===`)
281pub 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    // PHP 使用 === 严格比较
293    value == &other
294}
295
296/// 验证是否和某个字段的值是否不同 — 对齐 PHP `different`
297///
298/// 对齐 PHP `Validate.php` 第 738-741 行
299///
300/// ## PHP 行为
301///
302/// `return $this->getDataValue($data, $rule) != $value;`(松散比较 `!=`)
303pub 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
308// ============================================================================
309// 范围类规则
310// ============================================================================
311
312/// 验证是否在范围内 — 对齐 PHP `in`
313///
314/// 对齐 PHP `Validate.php` 第 1275-1278 行
315///
316/// ## PHP 行为
317///
318/// `return in_array($value, is_array($rule) ? $rule : explode(',', $rule));`
319///
320/// **注意**:PHP `in_array` 默认是松散比较。
321pub 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
332/// 验证是否不在某个范围 — 对齐 PHP `notIn`
333///
334/// 对齐 PHP `Validate.php` 第 1287-1290 行
335pub fn not_in(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
336    !in_rule(value, rule, _data, _field)
337}
338
339/// between 验证数据 — 对齐 PHP `between`
340///
341/// 对齐 PHP `Validate.php` 第 1299-1307 行
342///
343/// ## PHP 行为
344///
345/// ```php
346/// [$min, $max] = explode(',', $rule);
347/// return $value >= $min && $value <= $max;
348/// ```
349pub 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    // PHP 松散比较
357    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
366/// notBetween 验证数据 — 对齐 PHP `notBetween`
367///
368/// 对齐 PHP `Validate.php` 第 1316-1324 行
369pub fn not_between(value: &Value, rule: &str, data: &Value, field: &str) -> bool {
370    !between(value, rule, data, field)
371}
372
373/// PHP 松散比较(Value 与 &str)
374fn value_loose_compare_str(value: &Value, other: &str) -> Option<std::cmp::Ordering> {
375    // 尝试数字比较(PHP 松散比较:数字字符串按数字比较)
376    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    // 字符串比较
382    Some(value_as_string(value).as_str().cmp(other))
383}
384
385// ============================================================================
386// 长度类规则
387// ============================================================================
388
389/// 计算值的长度(对齐 PHP `mb_strlen((string) $value)`)
390///
391/// - 数组:元素个数
392/// - 字符串:Unicode 字符数(对齐 PHP `mb_strlen`)
393/// - 其他:字符串表示的长度
394fn 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
403/// 验证数据长度 — 对齐 PHP `length`
404///
405/// 对齐 PHP `Validate.php` 第 1333-1351 行
406///
407/// ## PHP 行为
408///
409/// - 数组:`count($value)`
410/// - 字符串:`mb_strlen((string) $value)`
411/// - 如果 `rule` 包含 `,`,为长度区间 `[min, max]`
412/// - 否则为指定长度
413pub 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
427/// 验证数据最大长度 — 对齐 PHP `max`
428///
429/// 对齐 PHP `Validate.php` 第 1360-1371 行
430pub 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
436/// 验证数据最小长度 — 对齐 PHP `min`
437///
438/// 对齐 PHP `Validate.php` 第 1380-1391 行
439pub 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
445// ============================================================================
446// 日期类规则
447// ============================================================================
448
449/// 验证时间和日期是否符合指定格式 — 对齐 PHP `dateFormat`
450///
451/// 对齐 PHP `Validate.php` 第 1109-1113 行
452///
453/// ## PHP 行为
454///
455/// ```php
456/// $info = date_parse_from_format($rule, $value);
457/// return 0 == $info['warning_count'] && 0 == $info['error_count'];
458/// ```
459pub 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    // 尝试 NaiveDateTime 解析
466    if NaiveDateTime::parse_from_str(s, &chrono_fmt).is_ok() {
467        return true;
468    }
469    // 尝试 NaiveDate 解析
470    if NaiveDate::parse_from_str(s, &chrono_fmt).is_ok() {
471        return true;
472    }
473    false
474}
475
476/// 验证日期 — 对齐 PHP `after`
477///
478/// 对齐 PHP `Validate.php` 第 1401-1404 行
479///
480/// ## PHP 行为
481///
482/// `return strtotime($value) >= strtotime($rule);`
483pub 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
492/// 验证日期 — 对齐 PHP `before`
493///
494/// 对齐 PHP `Validate.php` 第 1414-1417 行
495pub 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
504/// 验证日期 — 对齐 PHP `afterWith`
505///
506/// 对齐 PHP `Validate.php` 第 1427-1431 行
507///
508/// ## PHP 行为
509///
510/// ```php
511/// $rule = $this->getDataValue($data, $rule);
512/// return !is_null($rule) && strtotime($value) >= strtotime($rule);
513/// ```
514pub 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
527/// 验证日期 — 对齐 PHP `beforeWith`
528///
529/// 对齐 PHP `Validate.php` 第 1441-1445 行
530pub 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
543/// 验证有效期 — 对齐 PHP `expire`
544///
545/// 对齐 PHP `Validate.php` 第 1454-1471 行
546///
547/// ## PHP 行为
548///
549/// ```php
550/// [$start, $end] = explode(',', $rule);
551/// if (!is_numeric($start)) { $start = strtotime($start); }
552/// if (!is_numeric($end)) { $end = strtotime($end); }
553/// return time() >= $start && time() <= $end;
554/// ```
555pub 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    // 对齐 PHP is_numeric 检查:数字直接作为时间戳,否则解析为时间戳
564    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
584// ============================================================================
585// 条件必须类规则
586// ============================================================================
587
588/// 验证某个字段等于某个值的时候必须 — 对齐 PHP `requireIf`
589///
590/// 对齐 PHP `Validate.php` 第 1200-1209 行
591///
592/// ## PHP 行为
593///
594/// ```php
595/// [$field, $val] = explode(',', $rule);
596/// if ($this->getDataValue($data, $field) == $val) {
597///     return !empty($value) || '0' == $value;
598/// }
599/// return true;
600/// ```
601pub 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        // 必须验证:对齐 PHP `!empty($value) || '0' == $value`
612        !is_empty_value(value) || matches!(value, Value::String(s) if s == "0")
613    } else {
614        true
615    }
616}
617
618/// 验证某个字段有值的情况下必须 — 对齐 PHP `requireWith`
619///
620/// 对齐 PHP `Validate.php` 第 1238-1247 行
621///
622/// ## PHP 行为
623///
624/// ```php
625/// $val = $this->getDataValue($data, $rule);
626/// if (!empty($val)) {
627///     return !empty($value) || '0' == $value;
628/// }
629/// return true;
630/// ```
631pub 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
640/// 验证某个字段没有值的情况下必须 — 对齐 PHP `requireWithout`
641///
642/// 对齐 PHP `Validate.php` 第 1257-1266 行
643pub 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
652// ============================================================================
653// IP 类规则
654// ============================================================================
655
656/// 验证是否有效 IP — 对齐 PHP `ip`
657///
658/// 对齐 PHP `Validate.php` 第 942-949 行
659///
660/// ## PHP 行为
661///
662/// ```php
663/// if (!in_array($rule, ['ipv4', 'ipv6'])) { $rule = 'ipv4'; }
664/// return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]);
665/// ```
666pub 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", // ipv4 或默认
674        Ok(IpAddr::V6(_)) => rule == "ipv6",
675        Err(_) => false,
676    }
677}
678
679/// 验证 IP 许可 — 对齐 PHP `allowIp`
680///
681/// 对齐 PHP `Validate.php` 第 1480-1483 行
682pub 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
691/// 验证 IP 禁用 — 对齐 PHP `denyIp`
692///
693/// 对齐 PHP `Validate.php` 第 1492-1495 行
694pub fn deny_ip(value: &Value, rule: &str, _data: &Value, _field: &str) -> bool {
695    !allow_ip(value, rule, _data, _field)
696}
697
698// ============================================================================
699// 域名类规则
700// ============================================================================
701
702/// 验证是否为有效的域名或 IP — 对齐 PHP `activeUrl`
703///
704/// 对齐 PHP `Validate.php` 第 926-933 行
705///
706/// ## PHP 行为
707///
708/// ```php
709/// if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) {
710///     $rule = 'MX';
711/// }
712/// return checkdnsrr($value, $rule);
713/// ```
714///
715/// ## Rust 实现
716///
717/// `checkdnsrr` 通过 DNS 查询验证域名是否有效。Rust 实现使用
718/// `std::net::ToSocketAddrs` 解析域名,能解析则视为有效(简化处理)。
719pub 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    // 空字符串不是有效域名(避免 ":80" 被解析为有效地址)
725    if s.is_empty() {
726        return false;
727    }
728    // 简化:使用 DNS 解析验证域名有效性
729    // 对齐 PHP checkdnsrr 的语义:能解析到记录即视为有效
730    use std::net::ToSocketAddrs;
731    let target = format!("{}:80", s);
732    target.to_socket_addrs().is_ok()
733}
734
735// ============================================================================
736// 内联单元测试
737// ============================================================================
738
739#[cfg(test)]
740mod tests {
741    use super::*;
742    use serde_json::json;
743
744    // ========================================================================
745    // 比较类规则测试
746    // ========================================================================
747
748    #[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        // PHP 行为:rule 为空时,从 field 推断 field_confirm
814        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        // PHP 行为:field 包含 _confirm 时,取前缀作为确认字段
822        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        // 松散比较:1 == "1" 为 true,所以 different 为 false
832        let data2 = json!({"other": "1"});
833        assert!(!different(&json!(1), "other", &data2, ""));
834    }
835
836    // ========================================================================
837    // 范围类规则测试
838    // ========================================================================
839
840    #[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        // 松散比较:"5" 在 "1,10" 区间内
872        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, "")); // 缺少 max
884    }
885
886    // ========================================================================
887    // 长度类规则测试
888    // ========================================================================
889
890    #[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        // 对齐 PHP mb_strlen:Unicode 字符按字符计数
906        assert!(length(&json!("中文"), "2", &Value::Null, ""));
907        assert!(!length(&json!("中文"), "4", &Value::Null, "")); // 不是字节长度
908    }
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    // ========================================================================
930    // 日期类规则测试
931    // ========================================================================
932
933    #[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        // PHP 行为:字段值为 null 时返回 false
990        let data = json!({});
991        assert!(!after_with(&json!("2024-01-02"), "missing", &data, ""));
992    }
993
994    #[test]
995    fn test_expire_with_timestamps() {
996        // 使用时间戳:过去的时间区间应返回 false
997        let now = Utc::now().timestamp();
998        let past_start = now - 7200; // 2 小时前
999        let past_end = now - 3600; // 1 小时前
1000        let rule = format!("{},{}", past_start, past_end);
1001        assert!(!expire(&Value::Null, &rule, &Value::Null, ""));
1002
1003        // 当前时间在区间内应返回 true
1004        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        // 使用日期字符串
1013        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    // ========================================================================
1021    // 条件必须类规则测试
1022    // ========================================================================
1023
1024    #[test]
1025    fn test_require_if_condition_met() {
1026        // type=login 时 username 必须非空
1027        let data = json!({"type": "login"});
1028        assert!(require_if(&json!("alice"), "type,login", &data, ""));
1029        // 空值不满足 require
1030        assert!(!require_if(&json!(""), "type,login", &data, ""));
1031        // "0" 视为非空(PHP 特殊行为)
1032        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        // 条件不满足时返回 true(不验证)
1039        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        // 其他字段为空时不验证
1053        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        // 其他字段为空时必须
1063        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        // 其他字段有值时不验证
1071        assert!(require_without(&json!(""), "other_field", &data, ""));
1072    }
1073
1074    // ========================================================================
1075    // IP 类规则测试
1076    // ========================================================================
1077
1078    #[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, "")); // 默认 ipv4
1083        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    // ========================================================================
1127    // 域名类规则测试
1128    // ========================================================================
1129
1130    #[test]
1131    fn test_active_url_valid_domain() {
1132        // 测试已知可解析的域名
1133        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    // ========================================================================
1149    // 辅助函数测试
1150    // ========================================================================
1151
1152    #[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); // Unicode 字符数
1187    }
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}