feat(plugin): 字段正则校验 — validation.pattern 支持

Cargo.toml 新增 regex 依赖。validate_data 函数扩展支持
FieldValidation.pattern 正则校验,空值非必填字段跳过校验,
校验失败时返回自定义 message 或默认提示。
This commit is contained in:
iven
2026-04-17 10:37:37 +08:00
parent dadb826804
commit 314580243e
2 changed files with 69 additions and 2 deletions

View File

@@ -24,3 +24,4 @@ async-trait = { workspace = true }
sha2 = { workspace = true } sha2 = { workspace = true }
base64 = "0.22" base64 = "0.22"
moka = { version = "0.12", features = ["sync"] } moka = { version = "0.12", features = ["sync"] }
regex = "1"

View File

@@ -471,17 +471,40 @@ pub async fn resolve_entity_info_cached(
Ok(info) Ok(info)
} }
/// 校验数据:检查 required 字段 /// 校验数据:检查 required 字段 + 正则校验
fn validate_data(data: &serde_json::Value, fields: &[PluginField]) -> AppResult<()> { fn validate_data(data: &serde_json::Value, fields: &[PluginField]) -> AppResult<()> {
let obj = data.as_object().ok_or_else(|| { let obj = data.as_object().ok_or_else(|| {
AppError::Validation("data 必须是 JSON 对象".to_string()) AppError::Validation("data 必须是 JSON 对象".to_string())
})?; })?;
for field in fields { for field in fields {
let label = field.display_name.as_deref().unwrap_or(&field.name);
// required 检查
if field.required && !obj.contains_key(&field.name) { if field.required && !obj.contains_key(&field.name) {
let label = field.display_name.as_deref().unwrap_or(&field.name);
return Err(AppError::Validation(format!("字段 '{}' 不能为空", label))); return Err(AppError::Validation(format!("字段 '{}' 不能为空", label)));
} }
// 正则校验
if let Some(validation) = &field.validation {
if let Some(pattern) = &validation.pattern {
if let Some(val) = obj.get(&field.name) {
let str_val = val.as_str().unwrap_or("");
if !str_val.is_empty() {
let re = regex::Regex::new(pattern)
.map_err(|e| AppError::Internal(format!("正则表达式编译失败: {}", e)))?;
if !re.is_match(str_val) {
let default_msg = format!("字段 '{}' 格式不正确", label);
let msg = validation.message.as_deref()
.unwrap_or(&default_msg);
return Err(AppError::Validation(msg.to_string()));
}
}
}
}
}
} }
Ok(()) Ok(())
} }
@@ -552,3 +575,46 @@ async fn validate_ref_entities(
} }
Ok(()) Ok(())
} }
#[cfg(test)]
mod validate_tests {
use super::*;
use crate::manifest::{FieldValidation, PluginField, PluginFieldType};
fn make_field(name: &str, pattern: Option<&str>, message: Option<&str>) -> PluginField {
PluginField {
name: name.to_string(),
field_type: PluginFieldType::String,
required: false,
validation: pattern.map(|p| FieldValidation {
pattern: Some(p.to_string()),
message: message.map(|m| m.to_string()),
}),
..PluginField::default_for_field()
}
}
#[test]
fn validate_phone_pattern_rejects_invalid() {
let fields = vec![make_field("phone", Some("^1[3-9]\\d{9}$"), Some("手机号格式不正确"))];
let data = serde_json::json!({"phone": "1234"});
let result = validate_data(&data, &fields);
assert!(result.is_err());
}
#[test]
fn validate_phone_pattern_accepts_valid() {
let fields = vec![make_field("phone", Some("^1[3-9]\\d{9}$"), Some("手机号格式不正确"))];
let data = serde_json::json!({"phone": "13812345678"});
let result = validate_data(&data, &fields);
assert!(result.is_ok());
}
#[test]
fn validate_empty_optional_field_skips_pattern() {
let fields = vec![make_field("phone", Some("^1[3-9]\\d{9}$"), None)];
let data = serde_json::json!({"phone": ""});
let result = validate_data(&data, &fields);
assert!(result.is_ok());
}
}