use std::sync::Arc;
use indexmap::IndexMap;
use once_cell::sync::Lazy;
use regex::Regex;
use serde_json::Value;
pub mod message;
pub mod rules;
pub mod scene;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ValidateError {
Single(String),
Batch(IndexMap<String, String>),
}
impl std::fmt::Display for ValidateError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValidateError::Single(msg) => write!(f, "{}", msg),
ValidateError::Batch(errors) => {
let entries: Vec<String> = errors
.iter()
.map(|(k, v)| format!("{}: {}", k, v))
.collect();
write!(f, "{}", entries.join("; "))
}
}
}
}
impl std::error::Error for ValidateError {}
#[derive(Debug, Clone)]
pub enum Rule {
Simple(String),
WithArgs(String, String),
Multiple(Vec<Rule>),
}
impl Rule {
pub fn from_string(s: &str) -> Self {
if s.contains('|') {
let parts: Vec<Rule> = s
.split('|')
.map(|p| {
if let Some((t, a)) = p.split_once(':') {
Rule::WithArgs(t.to_string(), a.to_string())
} else {
Rule::Simple(p.to_string())
}
})
.collect();
Rule::Multiple(parts)
} else if let Some((t, a)) = s.split_once(':') {
Rule::WithArgs(t.to_string(), a.to_string())
} else {
Rule::Simple(s.to_string())
}
}
pub fn to_list(&self) -> Vec<(String, String)> {
match self {
Rule::Simple(t) => vec![(t.clone(), String::new())],
Rule::WithArgs(t, a) => vec![(t.clone(), a.clone())],
Rule::Multiple(list) => list.iter().flat_map(|r| r.to_list()).collect(),
}
}
}
pub type RuleCallback = Arc<dyn Fn(&Value, &str, &Value) -> bool + Send + Sync>;
pub static DEFAULT_REGEX: Lazy<IndexMap<&'static str, &'static str>> = Lazy::new(|| {
let mut m = IndexMap::new();
m.insert("alpha", "/^[A-Za-z]+$/");
m.insert("alphaNum", "/^[A-Za-z0-9]+$/");
m.insert("alphaDash", "/^[A-Za-z0-9\\-\\_]+$/");
m.insert(
"chs",
"/^[\\x{4e00}-\\x{9fa5}\\x{9fa6}-\\x{9fef}\\x{3400}-\\x{4db5}\\x{20000}-\\x{2ebe0}]+$/u",
);
m.insert(
"chsAlpha",
"/^[\\x{4e00}-\\x{9fa5}\\x{9fa6}-\\x{9fef}\\x{3400}-\\x{4db5}\\x{20000}-\\x{2ebe0}a-zA-Z]+$/u",
);
m.insert(
"chsAlphaNum",
"/^[\\x{4e00}-\\x{9fa5}\\x{9fa6}-\\x{9fef}\\x{3400}-\\x{4db5}\\x{20000}-\\x{2ebe0}a-zA-Z0-9]+$/u",
);
m.insert(
"chsDash",
"/^[\\x{4e00}-\\x{9fa5}\\x{9fa6}-\\x{9fef}\\x{3400}-\\x{4db5}\\x{20000}-\\x{2ebe0}a-zA-Z0-9\\_\\-]+$/u",
);
m.insert("mobile", "/^1[3-9]\\d{9}$/");
m.insert(
"idCard",
"/(^[1-9]\\d{5}(18|19|([23]\\d))\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$)|(^[1-9]\\d{5}\\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\\d{3}$)/",
);
m.insert("zip", "/\\d{6}/");
m
});
pub static TYPE_MSG: Lazy<IndexMap<&'static str, &'static str>> = Lazy::new(|| {
let mut m = IndexMap::new();
m.insert("require", ":attribute require");
m.insert("must", ":attribute must");
m.insert("number", ":attribute must be numeric");
m.insert("integer", ":attribute must be integer");
m.insert("float", ":attribute must be float");
m.insert("boolean", ":attribute must be bool");
m.insert("email", ":attribute not a valid email address");
m.insert("mobile", ":attribute not a valid mobile");
m.insert("array", ":attribute must be a array");
m.insert("accepted", ":attribute must be yes,on or 1");
m.insert("date", ":attribute not a valid datetime");
m.insert("file", ":attribute not a valid file");
m.insert("image", ":attribute not a valid image");
m.insert("alpha", ":attribute must be alpha");
m.insert("alphaNum", ":attribute must be alpha-numeric");
m.insert(
"alphaDash",
":attribute must be alpha-numeric, dash, underscore",
);
m.insert("activeUrl", ":attribute not a valid domain or ip");
m.insert("chs", ":attribute must be chinese");
m.insert("chsAlpha", ":attribute must be chinese or alpha");
m.insert("chsAlphaNum", ":attribute must be chinese,alpha-numeric");
m.insert(
"chsDash",
":attribute must be chinese,alpha-numeric,underscore, dash",
);
m.insert("url", ":attribute not a valid url");
m.insert("ip", ":attribute not a valid ip");
m.insert("dateFormat", ":attribute must be dateFormat of :rule");
m.insert("in", ":attribute must be in :rule");
m.insert("notIn", ":attribute be notin :rule");
m.insert("between", ":attribute must between :1 - :2");
m.insert("notBetween", ":attribute not between :1 - :2");
m.insert("length", "size of :attribute must be :rule");
m.insert("max", "max size of :attribute must be :rule");
m.insert("min", "min size of :attribute must be :rule");
m.insert("after", ":attribute cannot be less than :rule");
m.insert("before", ":attribute cannot exceed :rule");
m.insert("expire", ":attribute not within :rule");
m.insert("allowIp", "access IP is not allowed");
m.insert("denyIp", "access IP denied");
m.insert("confirm", ":attribute out of accord with :2");
m.insert("different", ":attribute cannot be same with :2");
m.insert("egt", ":attribute must greater than or equal :rule");
m.insert("gt", ":attribute must greater than :rule");
m.insert("elt", ":attribute must less than or equal :rule");
m.insert("lt", ":attribute must less than :rule");
m.insert("eq", ":attribute must equal :rule");
m.insert("unique", ":attribute has exists");
m.insert("regex", ":attribute not conform to the rules");
m.insert("method", "invalid Request method");
m.insert("token", "invalid token");
m.insert("fileSize", "filesize not match");
m.insert("fileExt", "extensions to upload is not allowed");
m.insert("fileMime", "mimetype to upload is not allowed");
m
});
pub static ALIAS: Lazy<IndexMap<&'static str, &'static str>> = Lazy::new(|| {
let mut m = IndexMap::new();
m.insert(">", "gt");
m.insert(">=", "egt");
m.insert("<", "lt");
m.insert("<=", "elt");
m.insert("=", "eq");
m.insert("same", "eq");
m
});
pub struct Validate {
rule: IndexMap<String, Rule>,
message: IndexMap<String, String>,
field: IndexMap<String, String>,
regex: IndexMap<String, String>,
scene: IndexMap<String, Vec<String>>,
scene_callbacks: IndexMap<String, scene::SceneCallback>,
current_scene: Option<String>,
batch: bool,
only: Vec<String>,
remove: IndexMap<String, Option<Vec<String>>>,
append: IndexMap<String, Vec<String>>,
error: ValidateError,
type_callbacks: IndexMap<String, RuleCallback>,
lang: Option<Arc<dyn message::Lang>>,
}
impl Default for Validate {
fn default() -> Self {
Self::new()
}
}
impl Validate {
pub fn new() -> Self {
Self {
rule: IndexMap::new(),
message: IndexMap::new(),
field: IndexMap::new(),
regex: IndexMap::new(),
scene: IndexMap::new(),
scene_callbacks: IndexMap::new(),
current_scene: None,
batch: false,
only: Vec::new(),
remove: IndexMap::new(),
append: IndexMap::new(),
error: ValidateError::Single(String::new()),
type_callbacks: IndexMap::new(),
lang: None,
}
}
pub fn rule(mut self, name: &str, rule: &str) -> Self {
self.rule.insert(name.to_string(), Rule::from_string(rule));
self
}
pub fn message(mut self, messages: IndexMap<String, String>) -> Self {
for (k, v) in messages {
self.message.insert(k, v);
}
self
}
pub fn field(mut self, fields: IndexMap<String, String>) -> Self {
for (k, v) in fields {
self.field.insert(k, v);
}
self
}
pub fn scene(mut self, name: &str) -> Self {
self.current_scene = Some(name.to_string());
self
}
pub fn register_scene(mut self, name: &str, fields: Vec<String>) -> Self {
self.scene.insert(name.to_string(), fields);
self
}
pub fn register_scene_callback(mut self, name: &str, callback: scene::SceneCallback) -> Self {
self.scene_callbacks.insert(name.to_string(), callback);
self
}
pub fn has_scene(&self, name: &str) -> bool {
self.scene.contains_key(name) || self.scene_callbacks.contains_key(name)
}
pub fn batch(mut self, batch: bool) -> Self {
self.batch = batch;
self
}
pub fn only(mut self, fields: Vec<String>) -> Self {
self.only = fields;
self
}
pub fn remove(mut self, field: &str, rule: Option<Vec<String>>) -> Self {
self.remove.insert(field.to_string(), rule);
self
}
pub fn append(mut self, field: &str, rule: Vec<String>) -> Self {
self.append.insert(field.to_string(), rule);
self
}
pub fn only_mut(&mut self, fields: Vec<String>) {
self.only = fields;
}
pub fn remove_mut(&mut self, field: &str, rule: Option<Vec<String>>) {
self.remove.insert(field.to_string(), rule);
}
pub fn append_mut(&mut self, field: &str, rule: Vec<String>) {
self.append.insert(field.to_string(), rule);
}
pub fn extend(&mut self, type_name: &str, callback: RuleCallback) -> &mut Self {
self.type_callbacks.insert(type_name.to_string(), callback);
self
}
pub fn regex(mut self, name: &str, pattern: &str) -> Self {
self.regex.insert(name.to_string(), pattern.to_string());
self
}
pub fn set_lang(mut self, lang: Arc<dyn message::Lang>) -> Self {
self.lang = Some(lang);
self
}
pub fn check(&mut self, data: &Value) -> Result<(), ValidateError> {
let mut batch_errors: IndexMap<String, String> = IndexMap::new();
let mut single_error: Option<String> = None;
if let Some(scene_name) = self.current_scene.clone() {
self.only.clear();
self.append.clear();
self.remove.clear();
if let Some(callback) = self.scene_callbacks.get(&scene_name).cloned() {
callback(self);
} else if let Some(fields) = self.scene.get(&scene_name) {
self.only = fields.clone();
}
}
let rules: Vec<(String, Rule)> = self
.rule
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
let append_clone = self.append.clone();
for (key, rule) in &rules {
let (field_name, title) = if let Some(idx) = key.find('|') {
(key[..idx].to_string(), key[idx + 1..].to_string())
} else {
let title = self.field.get(key).cloned().unwrap_or_else(|| key.clone());
(key.clone(), title)
};
if !self.only.is_empty() && !self.only.contains(&field_name) {
continue;
}
let value = Self::get_data_value(data, &field_name);
let result = self.check_item(&field_name, &value, rule, data, &title, &[]);
if let Err(msg) = result {
if self.batch {
batch_errors.insert(field_name, msg);
} else {
single_error = Some(msg);
break;
}
}
}
let _ = &append_clone;
if self.batch {
if batch_errors.is_empty() {
Ok(())
} else {
self.error = ValidateError::Batch(batch_errors);
Err(self.error.clone())
}
} else {
match single_error {
None => Ok(()),
Some(msg) => {
self.error = ValidateError::Single(msg);
Err(self.error.clone())
}
}
}
}
pub fn check_rule(&self, value: &Value, rules: &str) -> Result<(), String> {
let rule = Rule::from_string(rules);
let empty_data = Value::Null;
match self.check_item("", value, &rule, &empty_data, "", &[]) {
Ok(()) => Ok(()),
Err(msg) => Err(msg),
}
}
fn check_item(
&self,
field: &str,
value: &Value,
rules: &Rule,
data: &Value,
title: &str,
msg: &[String],
) -> Result<(), String> {
if matches!(self.remove.get(field), Some(None)) && !self.append.contains_key(field) {
return Ok(());
}
let mut rule_list = rules.to_list();
if let Some(append_rules) = self.append.get(field) {
for ar in append_rules {
let extra = Rule::from_string(ar).to_list();
for e in extra {
if !rule_list.contains(&e) {
rule_list.push(e);
}
}
}
}
if rule_list.is_empty() {
return Ok(());
}
for (i, (rule_type, rule_args)) in rule_list.iter().enumerate() {
let (cb_type, args, info) = Self::get_validate_type(rule_type, rule_args);
let in_append = self
.append
.get(field)
.map(|a| a.iter().any(|x| x == &info))
.unwrap_or(false);
let in_remove = self
.remove
.get(field)
.and_then(|r| r.as_ref())
.map(|r| r.iter().any(|x| x == &info))
.unwrap_or(false);
if !in_append && in_remove {
continue;
}
let result = if let Some(cb) = self.type_callbacks.get(&cb_type) {
cb(value, &args, data)
} else if info == "must"
|| info.starts_with("require")
|| (!value.is_null() && !is_empty_string(value))
{
self.dispatch_builtin(&cb_type, value, &args, data, field, title)
} else {
true
};
if !result {
let message = if i < msg.len() && !msg[i].is_empty() {
msg[i].clone()
} else {
self.get_rule_msg(field, title, &info, &args)
};
return Err(message);
}
}
Ok(())
}
fn dispatch_builtin(
&self,
type_name: &str,
value: &Value,
rule: &str,
data: &Value,
field: &str,
_title: &str,
) -> bool {
match type_name {
"require" => Self::require(value, rule),
"must" => Self::must(value, rule),
"is" => {
if let Some(cb) = self.type_callbacks.get(rule) {
return cb(value, "", data);
}
Self::is(value, rule, data)
}
"regex" => Self::regex_validate(value, rule, &self.regex),
"eq" => rules::eq(value, rule, data, field),
"gt" => rules::gt(value, rule, data, field),
"egt" => rules::egt(value, rule, data, field),
"lt" => rules::lt(value, rule, data, field),
"elt" => rules::elt(value, rule, data, field),
"confirm" => rules::confirm(value, rule, data, field),
"different" => rules::different(value, rule, data, field),
"in" => rules::in_rule(value, rule, data, field),
"notIn" => rules::not_in(value, rule, data, field),
"between" => rules::between(value, rule, data, field),
"notBetween" => rules::not_between(value, rule, data, field),
"length" => rules::length(value, rule, data, field),
"max" => rules::max(value, rule, data, field),
"min" => rules::min(value, rule, data, field),
"dateFormat" => rules::date_format(value, rule, data, field),
"after" => rules::after(value, rule, data, field),
"before" => rules::before(value, rule, data, field),
"afterWith" => rules::after_with(value, rule, data, field),
"beforeWith" => rules::before_with(value, rule, data, field),
"expire" => rules::expire(value, rule, data, field),
"requireIf" => rules::require_if(value, rule, data, field),
"requireWith" => rules::require_with(value, rule, data, field),
"requireWithout" => rules::require_without(value, rule, data, field),
"ip" => rules::ip(value, rule, data, field),
"allowIp" => rules::allow_ip(value, rule, data, field),
"denyIp" => rules::deny_ip(value, rule, data, field),
"activeUrl" => rules::active_url(value, rule, data, field),
_ => {
true
}
}
}
pub fn get_data_value(data: &Value, key: &str) -> Value {
if key.parse::<i64>().is_ok() || key.parse::<f64>().is_ok() {
return Value::String(key.to_string());
}
if key.contains('.') {
let mut current = data;
for part in key.split('.') {
match current.get(part) {
Some(v) => current = v,
None => return Value::Null,
}
}
return current.clone();
}
data.get(key).cloned().unwrap_or(Value::Null)
}
pub fn get_validate_type(rule_type: &str, rule_args: &str) -> (String, String, String) {
let resolved_type = if let Some(&alias) = ALIAS.get(rule_type) {
alias.to_string()
} else {
rule_type.to_string()
};
const PHP_METHODS: &[&str] = &[
"must",
"is",
"confirm",
"different",
"egt",
"gt",
"elt",
"lt",
"eq",
"activeUrl",
"ip",
"dateFormat",
"requireIf",
"requireCallback",
"requireWith",
"requireWithout",
"in",
"notIn",
"between",
"notBetween",
"length",
"max",
"min",
"after",
"before",
"afterWith",
"beforeWith",
"expire",
"allowIp",
"denyIp",
"regex",
];
if !rule_args.is_empty() {
(resolved_type.clone(), rule_args.to_string(), resolved_type)
} else if PHP_METHODS.contains(&resolved_type.as_str()) {
(resolved_type.clone(), String::new(), resolved_type)
} else {
("is".to_string(), resolved_type.clone(), resolved_type)
}
}
pub fn get_rule_msg(&self, field: &str, title: &str, type_name: &str, rule: &str) -> String {
let key1 = format!("{}.{}", field, type_name);
if let Some(msg) = self.message.get(&key1) {
return self.parse_error_msg_with_lang(msg, rule, title);
}
if let Some(msg) = self.message.get(field) {
return self.parse_error_msg_with_lang(msg, rule, title);
}
if let Some(&msg) = TYPE_MSG.get(type_name) {
return self.parse_error_msg_with_lang(msg, rule, title);
}
if type_name.starts_with("require") {
if let Some(&msg) = TYPE_MSG.get("require") {
return self.parse_error_msg_with_lang(msg, rule, title);
}
}
let suffix = if let Some(lang) = &self.lang {
lang.get("not conform to the rules")
} else {
"not conform to the rules".to_string()
};
format!("{}{}", title, suffix)
}
pub fn parse_error_msg_with_lang(&self, msg: &str, rule: &str, title: &str) -> String {
let translated = message::translate_msg(msg, self.lang.as_ref());
Self::parse_error_msg(&translated, rule, title)
}
pub fn parse_error_msg(msg: &str, rule: &str, title: &str) -> String {
let mut result = msg.to_string();
if !result.contains(':') {
return result;
}
let parts: Vec<&str> = if rule.contains(',') {
let split: Vec<&str> = rule.split(',').collect();
split
} else {
vec!["", "", ""]
};
let p1 = parts.first().copied().unwrap_or("");
let p2 = parts.get(1).copied().unwrap_or("");
let p3 = parts.get(2).copied().unwrap_or("");
result = result.replace(":attribute", title);
result = result.replace(":1", p1);
result = result.replace(":2", p2);
result = result.replace(":3", p3);
if result.contains(":rule") {
result = result.replace(":rule", rule);
}
result
}
pub fn get_error(&self) -> &ValidateError {
&self.error
}
pub fn require(value: &Value, _rule: &str) -> bool {
!is_empty_value(value) || matches!(value, Value::String(s) if s == "0")
}
pub fn must(value: &Value, _rule: &str) -> bool {
!is_empty_value(value) || matches!(value, Value::String(s) if s == "0")
}
pub fn is(value: &Value, rule: &str, _data: &Value) -> bool {
let rule = rule.trim();
match rule {
"require" => {
!is_empty_value(value) || matches!(value, Value::String(s) if s == "0")
}
"accepted" => {
matches!(
value,
Value::Number(n) if n.as_i64() == Some(1)
) || matches!(value, Value::String(s) if s == "1" || s == "on" || s == "yes")
}
"date" => is_valid_date(value),
"boolean" | "bool" => {
matches!(value, Value::Bool(_) | Value::Null)
|| matches!(value, Value::Number(n) if n.as_i64() == Some(0) || n.as_i64() == Some(1))
|| matches!(value, Value::String(s) if s == "0" || s == "1")
}
"number" => {
value.is_number() || matches!(value, Value::String(s) if s.parse::<f64>().is_ok())
}
"integer" => {
value.is_i64() || matches!(value, Value::String(s) if s.parse::<i64>().is_ok())
}
"float" => {
value.is_number() || matches!(value, Value::String(s) if s.parse::<f64>().is_ok())
}
"array" => value.is_array(),
"email" => is_valid_email(value),
"url" => is_valid_url(value),
"ip" => is_valid_ip(value),
"macAddr" => is_valid_mac(value),
"alpha" => regex_match_default(value, "alpha"),
"alphaNum" => regex_match_default(value, "alphaNum"),
"alphaDash" => regex_match_default(value, "alphaDash"),
"chs" => regex_match_default(value, "chs"),
"chsAlpha" => regex_match_default(value, "chsAlpha"),
"chsAlphaNum" => regex_match_default(value, "chsAlphaNum"),
"chsDash" => regex_match_default(value, "chsDash"),
"mobile" => regex_match_default(value, "mobile"),
"idCard" => regex_match_default(value, "idCard"),
"zip" => regex_match_default(value, "zip"),
_ => {
true
}
}
}
pub fn regex_validate(
value: &Value,
rule: &str,
custom_regex: &IndexMap<String, String>,
) -> bool {
let pattern = if let Some(p) = custom_regex.get(rule) {
p.clone()
} else if let Some(&p) = DEFAULT_REGEX.get(rule) {
p.to_string()
} else {
if rule.starts_with('/') {
rule.to_string()
} else {
format!("/^{}/$", rule)
}
};
let s = match value {
Value::String(s) => s.clone(),
Value::Number(n) => n.to_string(),
Value::Bool(b) => b.to_string(),
_ => return false,
};
let rust_pattern = php_regex_to_rust(&pattern);
match Regex::new(&rust_pattern) {
Ok(re) => re.is_match(&s),
Err(_) => false,
}
}
}
pub(crate) fn is_empty_value(value: &Value) -> bool {
match value {
Value::Null => true,
Value::Bool(b) => !b,
Value::String(s) => s.is_empty() || s == "0",
Value::Number(n) => n.as_f64().map(|f| f == 0.0).unwrap_or(true),
Value::Array(a) => a.is_empty(),
Value::Object(o) => o.is_empty(),
}
}
fn is_empty_string(value: &Value) -> bool {
matches!(value, Value::String(s) if s.is_empty())
}
fn is_valid_date(value: &Value) -> bool {
let s = match value {
Value::String(s) => s,
_ => return false,
};
use chrono::{DateTime, NaiveDate, NaiveDateTime};
if DateTime::parse_from_rfc3339(s).is_ok() {
return true;
}
if NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S").is_ok() {
return true;
}
if NaiveDate::parse_from_str(s, "%Y-%m-%d").is_ok() {
return true;
}
if NaiveDateTime::parse_from_str(s, "%Y/%m/%d %H:%M:%S").is_ok() {
return true;
}
if NaiveDate::parse_from_str(s, "%Y/%m/%d").is_ok() {
return true;
}
false
}
fn is_valid_email(value: &Value) -> bool {
let s = match value {
Value::String(s) => s,
_ => return false,
};
let email_re =
Lazy::new(|| Regex::new(r"^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$").unwrap());
email_re.is_match(s)
}
fn is_valid_url(value: &Value) -> bool {
let s = match value {
Value::String(s) => s,
_ => return false,
};
s.starts_with("http://")
|| s.starts_with("https://")
|| s.starts_with("ftp://")
|| s.starts_with("ftps://")
|| s.starts_with("mailto:")
|| s.starts_with("tel:")
|| s.starts_with("file://")
}
fn is_valid_ip(value: &Value) -> bool {
let s = match value {
Value::String(s) => s,
Value::Number(n) => {
let _ = n;
return false;
}
_ => return false,
};
use std::net::IpAddr;
s.parse::<IpAddr>().is_ok()
}
fn is_valid_mac(value: &Value) -> bool {
let s = match value {
Value::String(s) => s,
_ => return false,
};
let mac_re = Lazy::new(|| Regex::new(r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$").unwrap());
mac_re.is_match(s)
}
fn regex_match_default(value: &Value, regex_name: &str) -> bool {
let s = match value {
Value::String(s) => s.clone(),
Value::Number(n) => n.to_string(),
Value::Bool(b) => b.to_string(),
_ => return false,
};
if let Some(&pattern) = DEFAULT_REGEX.get(regex_name) {
let rust_pattern = php_regex_to_rust(pattern);
match Regex::new(&rust_pattern) {
Ok(re) => re.is_match(&s),
Err(_) => false,
}
} else {
false
}
}
fn php_regex_to_rust(pattern: &str) -> String {
let mut result = pattern.to_string();
while let Some(start) = result.find("\\x{") {
if let Some(end) = result[start..].find('}') {
let hex = &result[start + 3..start + end];
let replacement = format!("\\u{{{}}}", hex);
result.replace_range(start..start + end + 1, &replacement);
} else {
break;
}
}
if result.starts_with('/') && result.len() > 2 {
let flags_start = result.rfind('/').unwrap_or(result.len() - 1);
if flags_start > 0 {
let _inner = &result[1..flags_start];
let flags = &result[flags_start + 1..];
let mut prefix = String::new();
if flags.contains('i') {
prefix.push_str("(?i)");
}
if flags.contains('m') {
prefix.push_str("(?m)");
}
if flags.contains('s') {
prefix.push_str("(?s)");
}
return format!("{}{}", prefix, _inner);
}
}
result
}
#[cfg(test)]
#[allow(clippy::approx_constant)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_validate_error_single_display() {
let err = ValidateError::Single("name require".to_string());
assert_eq!(format!("{}", err), "name require");
}
#[test]
fn test_validate_error_batch_display() {
let mut errors = IndexMap::new();
errors.insert("name".to_string(), "name require".to_string());
errors.insert("age".to_string(), "age must be integer".to_string());
let err = ValidateError::Batch(errors);
let displayed = format!("{}", err);
assert!(displayed.contains("name: name require"));
assert!(displayed.contains("age: age must be integer"));
}
#[test]
fn test_validate_error_clone_eq() {
let err1 = ValidateError::Single("test".to_string());
let err2 = err1.clone();
assert_eq!(err1, err2);
}
#[test]
fn test_rule_from_string_simple() {
let r = Rule::from_string("require");
match r {
Rule::Simple(s) => assert_eq!(s, "require"),
_ => panic!("expected Simple"),
}
}
#[test]
fn test_rule_from_string_with_args() {
let r = Rule::from_string("in:1,2,3");
match r {
Rule::WithArgs(t, a) => {
assert_eq!(t, "in");
assert_eq!(a, "1,2,3");
}
_ => panic!("expected WithArgs"),
}
}
#[test]
fn test_rule_from_string_multiple() {
let r = Rule::from_string("require|in:1,2,3");
match r {
Rule::Multiple(list) => {
assert_eq!(list.len(), 2);
assert!(matches!(list[0], Rule::Simple(ref s) if s == "require"));
assert!(
matches!(list[1], Rule::WithArgs(ref t, ref a) if t == "in" && a == "1,2,3")
);
}
_ => panic!("expected Multiple"),
}
}
#[test]
fn test_rule_to_list_simple() {
let r = Rule::Simple("require".to_string());
let list = r.to_list();
assert_eq!(list, vec![("require".to_string(), String::new())]);
}
#[test]
fn test_rule_to_list_multiple() {
let r = Rule::from_string("require|in:1,2,3|email");
let list = r.to_list();
assert_eq!(list.len(), 3);
assert_eq!(list[0], ("require".to_string(), String::new()));
assert_eq!(list[1], ("in".to_string(), "1,2,3".to_string()));
assert_eq!(list[2], ("email".to_string(), String::new()));
}
#[test]
fn test_validate_new_empty() {
let v = Validate::new();
assert!(!v.batch);
assert!(v.current_scene.is_none());
assert!(v.rule.is_empty());
assert!(v.message.is_empty());
assert!(v.field.is_empty());
assert!(v.scene.is_empty());
assert!(v.only.is_empty());
assert!(v.remove.is_empty());
assert!(v.append.is_empty());
assert!(v.type_callbacks.is_empty());
}
#[test]
fn test_validate_rule_builder() {
let v = Validate::new()
.rule("name", "require")
.rule("age", "integer");
assert_eq!(v.rule.len(), 2);
assert!(v.rule.contains_key("name"));
assert!(v.rule.contains_key("age"));
}
#[test]
fn test_validate_message_builder() {
let mut msgs = IndexMap::new();
msgs.insert("name.require".to_string(), "名称必须".to_string());
let v = Validate::new().message(msgs);
assert_eq!(v.message.get("name.require"), Some(&"名称必须".to_string()));
}
#[test]
fn test_validate_field_builder() {
let mut fields = IndexMap::new();
fields.insert("name".to_string(), "名称".to_string());
let v = Validate::new().field(fields);
assert_eq!(v.field.get("name"), Some(&"名称".to_string()));
}
#[test]
fn test_validate_scene_builder() {
let v = Validate::new()
.register_scene(
"login",
vec!["username".to_string(), "password".to_string()],
)
.scene("login");
assert_eq!(v.current_scene, Some("login".to_string()));
assert!(v.has_scene("login"));
assert!(!v.has_scene("register"));
}
#[test]
fn test_validate_batch_builder() {
let v = Validate::new().batch(true);
assert!(v.batch);
}
#[test]
fn test_validate_only_builder() {
let v = Validate::new().only(vec!["name".to_string()]);
assert_eq!(v.only, vec!["name".to_string()]);
}
#[test]
fn test_validate_remove_builder() {
let v = Validate::new()
.remove("name", None)
.remove("age", Some(vec!["integer".to_string()]));
assert!(matches!(v.remove.get("name"), Some(None)));
assert!(
matches!(v.remove.get("age"), Some(Some(ref r)) if r == &vec!["integer".to_string()])
);
}
#[test]
fn test_validate_append_builder() {
let v = Validate::new().append("name", vec!["email".to_string()]);
assert_eq!(v.append.get("name"), Some(&vec!["email".to_string()]));
}
#[test]
fn test_validate_regex_builder() {
let v = Validate::new().regex("custom", r"^\d{4}$");
assert_eq!(v.regex.get("custom"), Some(&r"^\d{4}$".to_string()));
}
#[test]
fn test_static_default_regex_contains_all() {
assert!(DEFAULT_REGEX.contains_key("alpha"));
assert!(DEFAULT_REGEX.contains_key("alphaNum"));
assert!(DEFAULT_REGEX.contains_key("alphaDash"));
assert!(DEFAULT_REGEX.contains_key("chs"));
assert!(DEFAULT_REGEX.contains_key("chsAlpha"));
assert!(DEFAULT_REGEX.contains_key("chsAlphaNum"));
assert!(DEFAULT_REGEX.contains_key("chsDash"));
assert!(DEFAULT_REGEX.contains_key("mobile"));
assert!(DEFAULT_REGEX.contains_key("idCard"));
assert!(DEFAULT_REGEX.contains_key("zip"));
}
#[test]
fn test_static_type_msg_contains_all() {
assert!(TYPE_MSG.contains_key("require"));
assert!(TYPE_MSG.contains_key("must"));
assert!(TYPE_MSG.contains_key("number"));
assert!(TYPE_MSG.contains_key("email"));
assert!(TYPE_MSG.contains_key("mobile"));
assert!(TYPE_MSG.contains_key("in"));
assert!(TYPE_MSG.contains_key("notIn"));
assert!(TYPE_MSG.contains_key("between"));
assert!(TYPE_MSG.contains_key("length"));
assert!(TYPE_MSG.contains_key("max"));
assert!(TYPE_MSG.contains_key("min"));
assert!(TYPE_MSG.contains_key("eq"));
assert!(TYPE_MSG.contains_key("gt"));
assert!(TYPE_MSG.contains_key("egt"));
assert!(TYPE_MSG.contains_key("lt"));
assert!(TYPE_MSG.contains_key("elt"));
assert!(TYPE_MSG.contains_key("confirm"));
assert!(TYPE_MSG.contains_key("different"));
assert!(TYPE_MSG.contains_key("regex"));
}
#[test]
fn test_static_alias_contains_all() {
assert_eq!(ALIAS.get(">"), Some(&"gt"));
assert_eq!(ALIAS.get(">="), Some(&"egt"));
assert_eq!(ALIAS.get("<"), Some(&"lt"));
assert_eq!(ALIAS.get("<="), Some(&"elt"));
assert_eq!(ALIAS.get("="), Some(&"eq"));
assert_eq!(ALIAS.get("same"), Some(&"eq"));
}
#[test]
fn test_get_data_value_simple() {
let data = json!({"name": "Alice", "age": 30});
assert_eq!(Validate::get_data_value(&data, "name"), json!("Alice"));
assert_eq!(Validate::get_data_value(&data, "age"), json!(30));
}
#[test]
fn test_get_data_value_missing_field() {
let data = json!({"name": "Alice"});
assert_eq!(Validate::get_data_value(&data, "missing"), Value::Null);
}
#[test]
fn test_get_data_value_nested_dot_notation() {
let data = json!({"user": {"profile": {"age": 25}}});
assert_eq!(
Validate::get_data_value(&data, "user.profile.age"),
json!(25)
);
}
#[test]
fn test_get_data_value_nested_missing_intermediate() {
let data = json!({"user": {"name": "Alice"}});
assert_eq!(
Validate::get_data_value(&data, "user.profile.age"),
Value::Null
);
}
#[test]
fn test_get_data_value_numeric_key_php_bug() {
let data = json!({"123": "value"});
assert_eq!(
Validate::get_data_value(&data, "123"),
json!("123") );
}
#[test]
fn test_get_data_value_float_numeric_key() {
let data = json!({});
assert_eq!(Validate::get_data_value(&data, "3.14"), json!("3.14"));
}
#[test]
fn test_get_validate_type_no_alias() {
let (t, a, i) = Validate::get_validate_type("require", "");
assert_eq!(t, "is");
assert_eq!(a, "require");
assert_eq!(i, "require");
}
#[test]
fn test_get_validate_type_with_args() {
let (t, a, i) = Validate::get_validate_type("in", "1,2,3");
assert_eq!(t, "in");
assert_eq!(a, "1,2,3");
assert_eq!(i, "in");
}
#[test]
fn test_get_validate_type_alias_gt() {
let (t, a, i) = Validate::get_validate_type(">", "");
assert_eq!(t, "gt");
assert_eq!(a, "");
assert_eq!(i, "gt");
}
#[test]
fn test_get_validate_type_alias_same_to_eq() {
let (t, a, i) = Validate::get_validate_type("same", "");
assert_eq!(t, "eq");
assert_eq!(a, "");
assert_eq!(i, "eq");
}
#[test]
fn test_get_validate_type_must_method_exists() {
let (t, a, i) = Validate::get_validate_type("must", "");
assert_eq!(t, "must");
assert_eq!(a, "");
assert_eq!(i, "must");
}
#[test]
fn test_get_validate_type_integer_via_is() {
let (t, a, i) = Validate::get_validate_type("integer", "");
assert_eq!(t, "is");
assert_eq!(a, "integer");
assert_eq!(i, "integer");
}
#[test]
fn test_parse_error_msg_attribute_replacement() {
let result = Validate::parse_error_msg(":attribute require", "", "名称");
assert_eq!(result, "名称 require");
}
#[test]
fn test_parse_error_msg_rule_replacement() {
let result = Validate::parse_error_msg(":attribute must be in :rule", "1,2,3", "状态");
assert_eq!(result, "状态 must be in 1,2,3");
}
#[test]
fn test_parse_error_msg_numbered_replacement() {
let result = Validate::parse_error_msg(":attribute must between :1 - :2", "10,20", "年龄");
assert_eq!(result, "年龄 must between 10 - 20");
}
#[test]
fn test_parse_error_msg_no_colon_returns_as_is() {
let result = Validate::parse_error_msg("access IP denied", "1.2.3.4", "ip");
assert_eq!(result, "access IP denied");
}
#[test]
fn test_parse_error_msg_multiple_placeholders() {
let result = Validate::parse_error_msg(
":attribute must equal :rule and between :1 - :2",
"5,1,10",
"值",
);
assert_eq!(result, "值 must equal 5,1,10 and between 5 - 1");
}
#[test]
fn test_get_rule_msg_priority_field_type() {
let mut msgs = IndexMap::new();
msgs.insert("name.require".to_string(), "名称必须填写".to_string());
let v = Validate::new().message(msgs);
let msg = v.get_rule_msg("name", "名称", "require", "");
assert_eq!(msg, "名称必须填写");
}
#[test]
fn test_get_rule_msg_priority_field_only() {
let mut msgs = IndexMap::new();
msgs.insert("name".to_string(), "名称错误".to_string());
let v = Validate::new().message(msgs);
let msg = v.get_rule_msg("name", "名称", "require", "");
assert_eq!(msg, "名称错误");
}
#[test]
fn test_get_rule_msg_priority_type_msg() {
let v = Validate::new();
let msg = v.get_rule_msg("email_field", "邮箱", "email", "");
assert_eq!(msg, "邮箱 not a valid email address");
}
#[test]
fn test_get_rule_msg_priority_require_prefix() {
let v = Validate::new();
let msg = v.get_rule_msg("field", "字段", "requireIf", "");
assert_eq!(msg, "字段 require");
}
#[test]
fn test_get_rule_msg_default_fallback() {
let v = Validate::new();
let msg = v.get_rule_msg("field", "字段", "unknownType", "");
assert_eq!(msg, "字段not conform to the rules");
}
#[test]
fn test_require_non_empty_string() {
assert!(Validate::require(&json!("hello"), ""));
assert!(Validate::require(&json!("0"), "")); }
#[test]
fn test_require_empty_values() {
assert!(!Validate::require(&Value::Null, ""));
assert!(!Validate::require(&json!(""), ""));
assert!(!Validate::require(&json!(0), ""));
assert!(!Validate::require(&json!(false), ""));
assert!(!Validate::require(&json!([]), ""));
assert!(!Validate::require(&json!({}), ""));
}
#[test]
fn test_must_equals_require() {
assert_eq!(
Validate::require(&json!("hello"), ""),
Validate::must(&json!("hello"), "")
);
assert_eq!(
Validate::require(&Value::Null, ""),
Validate::must(&Value::Null, "")
);
}
#[test]
fn test_is_require() {
assert!(Validate::is(&json!("hello"), "require", &Value::Null));
assert!(!Validate::is(&Value::Null, "require", &Value::Null));
}
#[test]
fn test_is_accepted() {
assert!(Validate::is(&json!("1"), "accepted", &Value::Null));
assert!(Validate::is(&json!("on"), "accepted", &Value::Null));
assert!(Validate::is(&json!("yes"), "accepted", &Value::Null));
assert!(!Validate::is(&json!("no"), "accepted", &Value::Null));
}
#[test]
fn test_is_boolean() {
assert!(Validate::is(&json!(true), "boolean", &Value::Null));
assert!(Validate::is(&json!(false), "boolean", &Value::Null));
assert!(Validate::is(&json!(0), "boolean", &Value::Null));
assert!(Validate::is(&json!(1), "boolean", &Value::Null));
assert!(Validate::is(&json!("0"), "boolean", &Value::Null));
assert!(Validate::is(&json!("1"), "boolean", &Value::Null));
assert!(!Validate::is(&json!(2), "boolean", &Value::Null));
}
#[test]
fn test_is_number() {
assert!(Validate::is(&json!(123), "number", &Value::Null));
assert!(Validate::is(&json!(3.14), "number", &Value::Null));
assert!(Validate::is(&json!("123"), "number", &Value::Null));
assert!(Validate::is(&json!("3.14"), "number", &Value::Null));
assert!(!Validate::is(&json!("abc"), "number", &Value::Null));
}
#[test]
fn test_is_integer() {
assert!(Validate::is(&json!(123), "integer", &Value::Null));
assert!(Validate::is(&json!("123"), "integer", &Value::Null));
assert!(!Validate::is(&json!(3.14), "integer", &Value::Null));
assert!(!Validate::is(&json!("3.14"), "integer", &Value::Null));
}
#[test]
fn test_is_float() {
assert!(Validate::is(&json!(3.14), "float", &Value::Null));
assert!(Validate::is(&json!("3.14"), "float", &Value::Null));
assert!(Validate::is(&json!(123), "float", &Value::Null));
}
#[test]
fn test_is_array() {
assert!(Validate::is(&json!([1, 2, 3]), "array", &Value::Null));
assert!(!Validate::is(&json!("string"), "array", &Value::Null));
assert!(!Validate::is(&json!({}), "array", &Value::Null));
}
#[test]
fn test_is_email() {
assert!(Validate::is(
&json!("user@example.com"),
"email",
&Value::Null
));
assert!(Validate::is(
&json!("user.name+tag@example.co.uk"),
"email",
&Value::Null
));
assert!(!Validate::is(&json!("invalid"), "email", &Value::Null));
assert!(!Validate::is(&json!("user@"), "email", &Value::Null));
}
#[test]
fn test_is_url() {
assert!(Validate::is(
&json!("http://example.com"),
"url",
&Value::Null
));
assert!(Validate::is(
&json!("https://example.com/path?q=1"),
"url",
&Value::Null
));
assert!(!Validate::is(&json!("example.com"), "url", &Value::Null));
}
#[test]
fn test_is_ip() {
assert!(Validate::is(&json!("127.0.0.1"), "ip", &Value::Null));
assert!(Validate::is(&json!("::1"), "ip", &Value::Null));
assert!(Validate::is(&json!("192.168.1.1"), "ip", &Value::Null));
assert!(!Validate::is(&json!("999.999.999.999"), "ip", &Value::Null));
assert!(!Validate::is(&json!("not.an.ip"), "ip", &Value::Null));
}
#[test]
fn test_is_mac_addr() {
assert!(Validate::is(
&json!("00:11:22:33:44:55"),
"macAddr",
&Value::Null
));
assert!(Validate::is(
&json!("00-11-22-33-44-55"),
"macAddr",
&Value::Null
));
assert!(!Validate::is(&json!("invalid"), "macAddr", &Value::Null));
}
#[test]
fn test_is_alpha() {
assert!(Validate::is(&json!("abc"), "alpha", &Value::Null));
assert!(Validate::is(&json!("ABC"), "alpha", &Value::Null));
assert!(!Validate::is(&json!("abc123"), "alpha", &Value::Null));
assert!(!Validate::is(&json!("abc_def"), "alpha", &Value::Null));
}
#[test]
fn test_is_alpha_num() {
assert!(Validate::is(&json!("abc123"), "alphaNum", &Value::Null));
assert!(Validate::is(&json!("ABC"), "alphaNum", &Value::Null));
assert!(!Validate::is(&json!("abc_123"), "alphaNum", &Value::Null));
}
#[test]
fn test_is_alpha_dash() {
assert!(Validate::is(&json!("abc123"), "alphaDash", &Value::Null));
assert!(Validate::is(
&json!("abc_def-123"),
"alphaDash",
&Value::Null
));
assert!(!Validate::is(&json!("abc def"), "alphaDash", &Value::Null));
}
#[test]
fn test_is_chs() {
assert!(Validate::is(&json!("中文"), "chs", &Value::Null));
assert!(!Validate::is(&json!("abc"), "chs", &Value::Null));
assert!(!Validate::is(&json!("中文abc"), "chs", &Value::Null));
}
#[test]
fn test_is_chs_alpha() {
assert!(Validate::is(&json!("中文abc"), "chsAlpha", &Value::Null));
assert!(Validate::is(&json!("中文"), "chsAlpha", &Value::Null));
assert!(!Validate::is(&json!("中文123"), "chsAlpha", &Value::Null));
}
#[test]
fn test_is_mobile() {
assert!(Validate::is(&json!("13812345678"), "mobile", &Value::Null));
assert!(Validate::is(&json!("19912345678"), "mobile", &Value::Null));
assert!(!Validate::is(&json!("12345678901"), "mobile", &Value::Null)); assert!(!Validate::is(&json!("1381234567"), "mobile", &Value::Null)); }
#[test]
fn test_is_date() {
assert!(Validate::is(&json!("2024-01-01"), "date", &Value::Null));
assert!(Validate::is(
&json!("2024-01-01 12:00:00"),
"date",
&Value::Null
));
assert!(Validate::is(&json!("2024/01/01"), "date", &Value::Null));
assert!(!Validate::is(&json!("invalid date"), "date", &Value::Null));
}
#[test]
fn test_regex_validate_default_pattern() {
let custom = IndexMap::new();
assert!(Validate::regex_validate(&json!("abc"), "alpha", &custom));
assert!(!Validate::regex_validate(&json!("123"), "alpha", &custom));
}
#[test]
fn test_regex_validate_custom_pattern() {
let mut custom = IndexMap::new();
custom.insert("custom".to_string(), r"^\d{4}$".to_string());
assert!(Validate::regex_validate(&json!("1234"), "custom", &custom));
assert!(!Validate::regex_validate(
&json!("12345"),
"custom",
&custom
));
}
#[test]
fn test_regex_validate_inline_pattern() {
let custom = IndexMap::new();
assert!(Validate::regex_validate(&json!("12345"), r"\d{5}", &custom));
assert!(!Validate::regex_validate(&json!("abc"), r"\d{5}", &custom));
}
#[test]
fn test_check_success_single_rule() {
let mut v = Validate::new().rule("name", "require");
let data = json!({"name": "Alice"});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_check_failure_single_rule() {
let mut v = Validate::new().rule("name", "require");
let data = json!({"name": ""});
let result = v.check(&data);
assert!(result.is_err());
match result.unwrap_err() {
ValidateError::Single(msg) => assert!(msg.contains("require")),
_ => panic!("expected Single error"),
}
}
#[test]
fn test_check_success_multiple_rules() {
let mut v = Validate::new()
.rule("name", "require")
.rule("age", "require|integer");
let data = json!({"name": "Alice", "age": 30});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_check_failure_multiple_rules_single_mode() {
let mut v = Validate::new()
.rule("name", "require")
.rule("age", "require|integer");
let data = json!({"name": "", "age": "not_int"});
let result = v.check(&data);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), ValidateError::Single(_)));
}
#[test]
fn test_check_batch_mode_collects_all_errors() {
let mut v = Validate::new()
.rule("name", "require")
.rule("age", "require|integer")
.batch(true);
let data = json!({"name": "", "age": "not_int"});
let result = v.check(&data);
assert!(result.is_err());
match result.unwrap_err() {
ValidateError::Batch(errors) => {
assert!(errors.contains_key("name"));
}
ValidateError::Single(_) => panic!("expected Batch error in batch mode"),
}
}
#[test]
fn test_check_missing_field_with_require() {
let mut v = Validate::new().rule("name", "require");
let data = json!({});
assert!(v.check(&data).is_err());
}
#[test]
fn test_check_missing_field_without_require_skips() {
let mut v = Validate::new().rule("age", "integer");
let data = json!({}); assert!(v.check(&data).is_ok());
}
#[test]
fn test_check_field_title_parsing() {
let mut v = Validate::new().rule("name|名称", "require");
let data = json!({"name": ""});
let result = v.check(&data);
assert!(result.is_err());
match result.unwrap_err() {
ValidateError::Single(msg) => assert!(msg.contains("名称")),
_ => panic!("expected Single error"),
}
}
#[test]
fn test_check_field_description_from_field_map() {
let mut fields = IndexMap::new();
fields.insert("name".to_string(), "用户名".to_string());
let mut v = Validate::new().field(fields).rule("name", "require");
let data = json!({"name": ""});
let result = v.check(&data);
assert!(result.is_err());
match result.unwrap_err() {
ValidateError::Single(msg) => assert!(msg.contains("用户名")),
_ => panic!("expected Single error"),
}
}
#[test]
fn test_check_custom_message_override() {
let mut msgs = IndexMap::new();
msgs.insert("name.require".to_string(), "名称必填".to_string());
let mut v = Validate::new().message(msgs).rule("name", "require");
let data = json!({"name": ""});
let result = v.check(&data);
match result.unwrap_err() {
ValidateError::Single(msg) => assert_eq!(msg, "名称必填"),
_ => panic!("expected Single error"),
}
}
#[test]
fn test_check_scene_filters_fields() {
let mut v = Validate::new()
.rule("name", "require")
.rule("age", "require")
.register_scene("login", vec!["name".to_string()])
.scene("login");
let data = json!({"name": "Alice"});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_check_scene_resets_state() {
let mut v = Validate::new()
.rule("name", "require")
.register_scene("s1", vec!["name".to_string()])
.only(vec!["other".to_string()]) .scene("s1"); let data = json!({"name": "Alice"});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_has_scene() {
let v = Validate::new()
.register_scene("login", vec!["name".to_string()])
.register_scene("register", vec!["name".to_string(), "email".to_string()]);
assert!(v.has_scene("login"));
assert!(v.has_scene("register"));
assert!(!v.has_scene("logout"));
}
#[test]
fn test_check_rule_success() {
let v = Validate::new();
assert!(v.check_rule(&json!("hello"), "require").is_ok());
assert!(v.check_rule(&json!(123), "integer").is_ok());
}
#[test]
fn test_check_rule_failure() {
let v = Validate::new();
assert!(v.check_rule(&Value::Null, "require").is_err());
assert!(v.check_rule(&json!("abc"), "integer").is_err());
}
#[test]
fn test_check_rule_multiple() {
let v = Validate::new();
assert!(v.check_rule(&json!(123), "require|integer").is_ok());
assert!(v.check_rule(&json!(""), "require|integer").is_err());
}
#[test]
fn test_check_dispatch_eq_rule() {
let v = Validate::new();
assert!(v.check_rule(&json!(5), "eq:5").is_ok());
assert!(v.check_rule(&json!("5"), "eq:5").is_ok()); assert!(v.check_rule(&json!(6), "eq:5").is_err());
}
#[test]
fn test_check_dispatch_gt_egt_lt_elt_rules() {
let v = Validate::new();
assert!(v.check_rule(&json!(6), "gt:5").is_ok());
assert!(v.check_rule(&json!(5), "gt:5").is_err());
assert!(v.check_rule(&json!(5), "egt:5").is_ok());
assert!(v.check_rule(&json!(4), "egt:5").is_err());
assert!(v.check_rule(&json!(4), "lt:5").is_ok());
assert!(v.check_rule(&json!(5), "lt:5").is_err());
assert!(v.check_rule(&json!(5), "elt:5").is_ok());
assert!(v.check_rule(&json!(6), "elt:5").is_err());
}
#[test]
fn test_check_dispatch_in_not_in_rules() {
let v = Validate::new();
assert!(v.check_rule(&json!(1), "in:1,2,3").is_ok());
assert!(v.check_rule(&json!("1"), "in:1,2,3").is_ok()); assert!(v.check_rule(&json!(4), "in:1,2,3").is_err());
assert!(v.check_rule(&json!(4), "notIn:1,2,3").is_ok());
assert!(v.check_rule(&json!(1), "notIn:1,2,3").is_err());
}
#[test]
fn test_check_dispatch_between_not_between_rules() {
let v = Validate::new();
assert!(v.check_rule(&json!(5), "between:1,10").is_ok());
assert!(v.check_rule(&json!("5"), "between:1,10").is_ok()); assert!(v.check_rule(&json!(0), "between:1,10").is_err());
assert!(v.check_rule(&json!(11), "between:1,10").is_err());
assert!(v.check_rule(&json!(0), "notBetween:1,10").is_ok());
}
#[test]
fn test_check_dispatch_length_max_min_rules() {
let v = Validate::new();
assert!(v.check_rule(&json!("abc"), "length:3").is_ok());
assert!(v.check_rule(&json!("abc"), "length:1,5").is_ok());
assert!(v.check_rule(&json!("abcdef"), "length:1,5").is_err());
assert!(v.check_rule(&json!("abc"), "max:5").is_ok());
assert!(v.check_rule(&json!("abcdef"), "max:5").is_err());
assert!(v.check_rule(&json!("abc"), "min:3").is_ok());
assert!(v.check_rule(&json!("ab"), "min:3").is_err());
}
#[test]
fn test_check_dispatch_length_unicode_chinese() {
let v = Validate::new();
assert!(v.check_rule(&json!("中文测试"), "length:4").is_ok());
assert!(v.check_rule(&json!("中"), "length:1").is_ok());
}
#[test]
fn test_check_dispatch_date_format_rule() {
let v = Validate::new();
assert!(v
.check_rule(&json!("2024-01-15"), "dateFormat:Y-m-d")
.is_ok());
assert!(v
.check_rule(&json!("2024/01/15"), "dateFormat:Y-m-d")
.is_err());
}
#[test]
fn test_check_dispatch_after_before_rules() {
let v = Validate::new();
assert!(v
.check_rule(&json!("2024-01-02"), "after:2024-01-01")
.is_ok());
assert!(v
.check_rule(&json!("2023-12-31"), "after:2024-01-01")
.is_err());
assert!(v
.check_rule(&json!("2023-12-31"), "before:2024-01-01")
.is_ok());
assert!(v
.check_rule(&json!("2024-01-02"), "before:2024-01-01")
.is_err());
}
#[test]
fn test_check_dispatch_ip_rule() {
let v = Validate::new();
assert!(v.check_rule(&json!("127.0.0.1"), "ip:ipv4").is_ok());
assert!(v.check_rule(&json!("127.0.0.1"), "ip").is_ok()); assert!(v.check_rule(&json!("::1"), "ip:ipv6").is_ok());
assert!(v.check_rule(&json!("::1"), "ip:ipv4").is_err());
}
#[test]
fn test_check_dispatch_confirm_rule_via_check() {
let mut v = Validate::new().rule("password", "require|confirm");
let data = json!({"password": "abc123", "password_confirm": "abc123"});
assert!(v.check(&data).is_ok());
let data = json!({"password": "abc123", "password_confirm": "different"});
assert!(v.check(&data).is_err());
}
#[test]
fn test_check_dispatch_different_rule_via_check() {
let mut v = Validate::new().rule("field1", "different:field2");
let data = json!({"field1": "abc", "field2": "xyz"});
assert!(v.check(&data).is_ok());
let data = json!({"field1": "abc", "field2": "abc"});
assert!(v.check(&data).is_err());
}
#[test]
fn test_check_dispatch_require_if_rule_via_check() {
let mut v = Validate::new().rule("username", "requireIf:type,login");
let data = json!({"type": "login", "username": "alice"});
assert!(v.check(&data).is_ok());
let data = json!({"type": "login", "username": ""});
assert!(v.check(&data).is_err());
let data = json!({"type": "register", "username": ""});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_check_dispatch_require_with_rule_via_check() {
let mut v = Validate::new().rule("email", "requireWith:contact");
let data = json!({"contact": "some_value", "email": "user@example.com"});
assert!(v.check(&data).is_ok());
let data = json!({"contact": "some_value", "email": ""});
assert!(v.check(&data).is_err());
let data = json!({"contact": "", "email": ""});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_check_dispatch_alias_operators() {
let v = Validate::new();
assert!(v.check_rule(&json!(6), ">:5").is_ok());
assert!(v.check_rule(&json!(5), ">=:5").is_ok());
assert!(v.check_rule(&json!(4), "<:5").is_ok());
assert!(v.check_rule(&json!(5), "<=:5").is_ok());
assert!(v.check_rule(&json!(5), "=:5").is_ok());
}
#[test]
fn test_extend_custom_rule_pass() {
let mut v = Validate::new();
v.extend(
"custom_even",
Arc::new(|value: &Value, _rule: &str, _data: &Value| {
value.as_i64().map(|n| n % 2 == 0).unwrap_or(false)
}),
);
let mut v = v.rule("num", "custom_even");
let data = json!({"num": 4});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_extend_custom_rule_fail() {
let mut v = Validate::new();
v.extend(
"custom_even",
Arc::new(|value: &Value, _rule: &str, _data: &Value| {
value.as_i64().map(|n| n % 2 == 0).unwrap_or(false)
}),
);
let mut v = v.rule("num", "custom_even");
let data = json!({"num": 5});
let result = v.check(&data);
assert!(result.is_err());
}
#[test]
fn test_extend_custom_rule_with_args() {
let mut v = Validate::new();
v.extend(
"custom_min",
Arc::new(|value: &Value, rule: &str, _data: &Value| {
let min: i64 = rule.parse().unwrap_or(0);
value.as_i64().map(|n| n >= min).unwrap_or(false)
}),
);
let mut v = v.rule("num", "custom_min:10");
assert!(v.check(&json!({"num": 15})).is_ok());
assert!(v.check(&json!({"num": 5})).is_err());
}
#[test]
fn test_get_error_after_check_failure() {
let mut v = Validate::new().rule("name", "require");
let data = json!({"name": ""});
let _ = v.check(&data);
let error = v.get_error();
match error {
ValidateError::Single(msg) => assert!(msg.contains("require")),
_ => panic!("expected Single error"),
}
}
#[test]
fn test_get_error_after_check_success() {
let mut v = Validate::new().rule("name", "require");
let data = json!({"name": "Alice"});
let _ = v.check(&data);
match v.get_error() {
ValidateError::Single(s) => assert!(s.is_empty()),
_ => panic!("expected Single (empty)"),
}
}
#[test]
fn test_php_bug_numeric_key_returns_key_itself() {
let data = json!({"123": "value", "456": "another"});
assert_eq!(Validate::get_data_value(&data, "123"), json!("123"));
}
#[test]
fn test_php_behavior_empty_value_skips_non_require_rules() {
let mut v = Validate::new().rule("age", "integer");
let data = json!({"age": ""});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_php_behavior_require_validates_string_zero() {
let mut v = Validate::new().rule("count", "require");
let data = json!({"count": "0"});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_php_behavior_scene_resets_only_append_remove() {
let mut v = Validate::new()
.rule("name", "require")
.rule("email", "require")
.only(vec!["email".to_string()]) .register_scene("scene1", vec!["name".to_string()])
.scene("scene1");
let data = json!({"name": "Alice"}); assert!(v.check(&data).is_ok());
}
#[test]
fn test_php_behavior_remove_all_rules_for_field() {
let mut v = Validate::new().rule("name", "require").remove("name", None);
let data = json!({}); assert!(v.check(&data).is_ok());
}
#[test]
fn test_php_behavior_remove_specific_rule() {
let mut v = Validate::new()
.rule("age", "require|integer")
.remove("age", Some(vec!["integer".to_string()]));
let data = json!({"age": "not_int"});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_php_behavior_append_adds_rule() {
let mut v = Validate::new()
.rule("contact", "require")
.append("contact", vec!["email".to_string()]);
assert!(v.check(&json!({"contact": "user@example.com"})).is_ok());
assert!(v.check(&json!({"contact": "invalid"})).is_err());
}
#[test]
fn test_php_behavior_get_rule_msg_lookup_chain() {
let mut msgs = IndexMap::new();
msgs.insert("name.require".to_string(), "优先级1".to_string());
msgs.insert("name".to_string(), "优先级2".to_string());
let v = Validate::new().message(msgs);
assert_eq!(v.get_rule_msg("name", "名称", "require", ""), "优先级1");
let mut msgs = IndexMap::new();
msgs.insert("name".to_string(), "优先级2".to_string());
let v = Validate::new().message(msgs);
assert_eq!(v.get_rule_msg("name", "名称", "require", ""), "优先级2");
let v = Validate::new();
assert_eq!(
v.get_rule_msg("name", "名称", "email", ""),
"名称 not a valid email address"
);
assert_eq!(
v.get_rule_msg("name", "名称", "requireIf", ""),
"名称 require"
);
assert_eq!(
v.get_rule_msg("name", "名称", "unknownType", ""),
"名称not conform to the rules"
);
}
#[test]
fn test_register_scene_callback_basic() {
let v = Validate::new().register_scene_callback("login", Arc::new(|_v| {}));
assert!(v.has_scene("login"));
assert!(!v.has_scene("register"));
}
#[test]
fn test_has_scene_checks_both_array_and_callback() {
let v = Validate::new()
.register_scene("array_scene", vec!["name".to_string()])
.register_scene_callback("callback_scene", Arc::new(|_v| {}));
assert!(v.has_scene("array_scene"));
assert!(v.has_scene("callback_scene"));
assert!(!v.has_scene("nonexistent"));
}
#[test]
fn test_scene_callback_sets_only() {
let mut v = Validate::new()
.rule("name", "require")
.rule("email", "require")
.register_scene_callback(
"login",
Arc::new(|v| {
v.only_mut(vec!["email".to_string()]);
}),
)
.scene("login");
let data = json!({"email": "test@example.com"});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_scene_callback_priority_over_array() {
let mut v = Validate::new()
.rule("name", "require")
.rule("email", "require")
.register_scene("conflict", vec!["name".to_string()])
.register_scene_callback(
"conflict",
Arc::new(|v| {
v.only_mut(vec!["email".to_string()]);
}),
)
.scene("conflict");
let data = json!({"email": "test@example.com"});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_scene_callback_can_modify_append() {
let mut v = Validate::new()
.rule("name", "require")
.register_scene_callback(
"strict",
Arc::new(|v| {
v.append_mut("name", vec!["max:5".to_string()]);
}),
)
.scene("strict");
let data = json!({"name": "Alice2"});
assert!(v.check(&data).is_err());
}
#[test]
fn test_scene_callback_can_modify_remove() {
let mut v = Validate::new()
.rule("name", "require|max:5")
.register_scene_callback(
"lenient",
Arc::new(|v| {
v.remove_mut("name", Some(vec!["max".to_string()]));
}),
)
.scene("lenient");
let data = json!({"name": "Alice2"});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_scene_callback_resets_state() {
let mut v = Validate::new()
.rule("name", "require")
.rule("email", "require")
.only(vec!["email".to_string()]) .register_scene_callback(
"scene1",
Arc::new(|v| {
v.only_mut(vec!["name".to_string()]);
}),
)
.scene("scene1");
let data = json!({"name": "Alice"});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_scene_callback_no_callback_no_array() {
let mut v = Validate::new()
.rule("name", "require")
.rule("email", "require")
.only(vec!["email".to_string()]) .scene("nonexistent"); let data = json!({"name": "Alice"}); assert!(v.check(&data).is_err());
}
#[test]
fn test_only_mut_method() {
let mut v = Validate::new();
v.only_mut(vec!["a".to_string(), "b".to_string()]);
let mut v = Validate::new()
.rule("a", "require")
.rule("b", "require")
.rule("c", "require");
v.only_mut(vec!["a".to_string(), "b".to_string()]);
let data = json!({"a": "x", "b": "y"}); assert!(v.check(&data).is_ok());
}
#[test]
fn test_append_mut_method() {
let mut v = Validate::new().rule("name", "require");
v.append_mut("name", vec!["max:3".to_string()]);
let data = json!({"name": "Alice"});
assert!(v.check(&data).is_err());
}
#[test]
fn test_remove_mut_method() {
let mut v = Validate::new().rule("name", "require|max:3");
v.remove_mut("name", Some(vec!["max".to_string()]));
let data = json!({"name": "Alice"});
assert!(v.check(&data).is_ok());
}
#[test]
fn test_php_behavior_scene_callback_mimics_scene_method() {
let mut v = Validate::new()
.rule("name", "require|max:25")
.rule("email", "require|email")
.rule("age", "integer")
.register_scene_callback(
"register",
Arc::new(|v| {
v.only_mut(vec![
"name".to_string(),
"email".to_string(),
"age".to_string(),
]);
v.append_mut("age", vec!["require".to_string()]);
}),
)
.scene("register");
let data = json!({
"name": "Alice",
"email": "test@example.com",
"age": 30
});
assert!(v.check(&data).is_ok());
let data2 = json!({
"name": "Alice",
"email": "test@example.com"
});
assert!(v.check(&data2).is_err());
}
#[test]
fn test_php_behavior_get_scene_method_priority() {
let mut v1 = Validate::new()
.rule("name", "require")
.rule("email", "require")
.register_scene_callback(
"cb_scene",
Arc::new(|v| {
v.only_mut(vec!["email".to_string()]);
}),
)
.scene("cb_scene");
let data1 = json!({"email": "test@example.com"}); assert!(v1.check(&data1).is_ok());
let mut v2 = Validate::new()
.rule("name", "require")
.rule("email", "require")
.register_scene("arr_scene", vec!["email".to_string()])
.scene("arr_scene");
let data2 = json!({"email": "test@example.com"}); assert!(v2.check(&data2).is_ok());
}
#[test]
fn test_php_behavior_scene_resets_all_three_state() {
let mut v = Validate::new()
.rule("name", "require|max:5")
.rule("email", "require")
.only(vec!["email".to_string()]) .append("name", vec!["max:3".to_string()]) .remove("email", None) .register_scene("reset", vec!["name".to_string()])
.scene("reset");
let data = json!({"name": "Alice"}); assert!(v.check(&data).is_ok());
}
#[test]
fn test_set_lang_basic_injection() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("require", "必须填写"));
let v = Validate::new().set_lang(lang);
assert!(v.lang.is_some());
}
#[test]
fn test_get_rule_msg_with_lang_default_translation() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("not conform to the rules", "不符合规则"));
let v = Validate::new().set_lang(lang);
let msg = v.get_rule_msg("field", "字段", "unknownType", "");
assert_eq!(msg, "字段不符合规则");
}
#[test]
fn test_get_rule_msg_with_lang_no_translation_returns_name() {
let lang: Arc<dyn message::Lang> = Arc::new(message::SimpleLang::new());
let v = Validate::new().set_lang(lang);
let msg = v.get_rule_msg("field", "字段", "unknownType", "");
assert_eq!(msg, "字段not conform to the rules");
}
#[test]
fn test_get_rule_msg_with_lang_percent_var_syntax() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("custom_msg", "自定义错误消息"));
let mut msgs = IndexMap::new();
msgs.insert("name".to_string(), "{%custom_msg}".to_string());
let v = Validate::new().message(msgs).set_lang(lang);
let msg = v.get_rule_msg("name", "名称", "require", "");
assert_eq!(msg, "自定义错误消息");
}
#[test]
fn test_get_rule_msg_with_lang_has_check() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("require", "必须填写"));
let mut msgs = IndexMap::new();
msgs.insert("name".to_string(), "require".to_string());
let v = Validate::new().message(msgs).set_lang(lang);
let msg = v.get_rule_msg("name", "名称", "require", "");
assert_eq!(msg, "必须填写");
}
#[test]
fn test_get_rule_msg_with_lang_percent_var_with_placeholders() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("require_msg", ":attribute 必填"));
let mut msgs = IndexMap::new();
msgs.insert("name".to_string(), "{%require_msg}".to_string());
let v = Validate::new().message(msgs).set_lang(lang);
let msg = v.get_rule_msg("name", "名称", "require", "");
assert_eq!(msg, "名称 必填");
}
#[test]
fn test_get_rule_msg_with_lang_has_check_with_placeholders() {
let lang: Arc<dyn message::Lang> = Arc::new(
message::SimpleLang::new().set("range_msg", ":attribute 必须在 :1 到 :2 之间"),
);
let mut msgs = IndexMap::new();
msgs.insert("age".to_string(), "range_msg".to_string());
let v = Validate::new().message(msgs).set_lang(lang);
let msg = v.get_rule_msg("age", "年龄", "between", "18,60");
assert_eq!(msg, "年龄 必须在 18 到 60 之间");
}
#[test]
fn test_parse_error_msg_with_lang_no_lang_skips_translation() {
let v = Validate::new();
let result = v.parse_error_msg_with_lang(":attribute require", "", "名称");
assert_eq!(result, "名称 require");
}
#[test]
fn test_parse_error_msg_with_lang_translates_first() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("custom_msg", ":attribute 自定义错误 :rule"));
let v = Validate::new().set_lang(lang);
let result = v.parse_error_msg_with_lang("custom_msg", "param1", "字段");
assert_eq!(result, "字段 自定义错误 param1");
}
#[test]
fn test_parse_error_msg_with_lang_percent_var_no_placeholders() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("plain_msg", "纯文本消息"));
let v = Validate::new().set_lang(lang);
let result = v.parse_error_msg_with_lang("{%plain_msg}", "rule", "字段");
assert_eq!(result, "纯文本消息");
}
#[test]
fn test_parse_error_msg_with_lang_no_colon_no_replacement() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("plain", "纯文本"));
let v = Validate::new().set_lang(lang);
let result = v.parse_error_msg_with_lang("plain", "rule", "字段");
assert_eq!(result, "纯文本");
}
#[test]
fn test_check_with_lang_translates_error_message() {
let lang: Arc<dyn message::Lang> = Arc::new(
message::SimpleLang::new()
.set("not conform to the rules", "不符合规则")
.set("require", ":attribute 必须填写"),
);
let mut v = Validate::new().rule("name", "require").set_lang(lang);
let data = json!({});
let result = v.check(&data);
assert!(result.is_err());
if let Err(ValidateError::Single(msg)) = result {
assert_eq!(msg, "name require");
} else {
panic!("Expected ValidateError::Single");
}
}
#[test]
fn test_check_with_lang_percent_var_in_message() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("name_required", "名称是必填字段"));
let mut msgs = IndexMap::new();
msgs.insert("name.require".to_string(), "{%name_required}".to_string());
let mut v = Validate::new()
.rule("name", "require")
.message(msgs)
.set_lang(lang);
let data = json!({});
let result = v.check(&data);
assert!(result.is_err());
if let Err(ValidateError::Single(msg)) = result {
assert_eq!(msg, "名称是必填字段");
} else {
panic!("Expected ValidateError::Single");
}
}
#[test]
fn test_check_with_lang_has_check_in_message() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("NAME_REQUIRED", "名称必填"));
let mut msgs = IndexMap::new();
msgs.insert("name.require".to_string(), "name_required".to_string());
let mut v = Validate::new()
.rule("name", "require")
.message(msgs)
.set_lang(lang);
let data = json!({});
let result = v.check(&data);
assert!(result.is_err());
if let Err(ValidateError::Single(msg)) = result {
assert_eq!(msg, "名称必填");
} else {
panic!("Expected ValidateError::Single");
}
}
#[test]
fn test_php_behavior_lang_translation_priority_percent_over_has() {
let lang: Arc<dyn message::Lang> = Arc::new(
message::SimpleLang::new()
.set("{%key}", "整体键") .set("key", "提取键"),
);
let mut msgs = IndexMap::new();
msgs.insert("field".to_string(), "{%key}".to_string());
let v = Validate::new().message(msgs).set_lang(lang);
let msg = v.get_rule_msg("field", "字段", "require", "");
assert_eq!(msg, "提取键");
}
#[test]
fn test_php_behavior_lang_default_case_uses_lang_get() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("not conform to the rules", " 不符合规则"));
let v = Validate::new().set_lang(lang);
let msg = v.get_rule_msg("field", "字段", "unknownType", "");
assert_eq!(msg, "字段 不符合规则");
}
#[test]
fn test_php_behavior_lang_translation_case_insensitive() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("RequireError", ":attribute 必填"));
let mut msgs = IndexMap::new();
msgs.insert("name".to_string(), "requireerror".to_string());
let v = Validate::new().message(msgs).set_lang(lang);
let msg = v.get_rule_msg("name", "名称", "require", "");
assert_eq!(msg, "名称 必填");
}
#[test]
fn test_php_behavior_lang_translation_not_found_returns_original() {
let lang: Arc<dyn message::Lang> = Arc::new(message::SimpleLang::new());
let mut msgs = IndexMap::new();
msgs.insert("name".to_string(), "nonexistent_key".to_string());
let v = Validate::new().message(msgs).set_lang(lang);
let msg = v.get_rule_msg("name", "名称", "require", "");
assert_eq!(msg, "nonexistent_key");
}
#[test]
fn test_php_behavior_lang_percent_var_extracts_key_via_substr() {
let lang: Arc<dyn message::Lang> =
Arc::new(message::SimpleLang::new().set("my.message.key", "我的消息"));
let mut msgs = IndexMap::new();
msgs.insert("name".to_string(), "{%my.message.key}".to_string());
let v = Validate::new().message(msgs).set_lang(lang);
let msg = v.get_rule_msg("name", "名称", "require", "");
assert_eq!(msg, "我的消息");
}
#[test]
fn test_php_behavior_lang_translation_with_full_pipeline() {
let lang: Arc<dyn message::Lang> = Arc::new(message::SimpleLang::new().set(
"between_msg",
":attribute 必须在 :1 - :2 之间(默认 :rule)",
));
let mut msgs = IndexMap::new();
msgs.insert("age.between".to_string(), "{%between_msg}".to_string());
let v = Validate::new().message(msgs).set_lang(lang);
let msg = v.get_rule_msg("age", "年龄", "between", "18,60");
assert_eq!(msg, "年龄 必须在 18 - 60 之间(默认 18,60)");
}
}