feat(plugin): 字段正则校验 — validation.pattern 支持
Cargo.toml 新增 regex 依赖。validate_data 函数扩展支持 FieldValidation.pattern 正则校验,空值非必填字段跳过校验, 校验失败时返回自定义 message 或默认提示。
This commit is contained in:
@@ -24,3 +24,4 @@ async-trait = { workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
base64 = "0.22"
|
||||
moka = { version = "0.12", features = ["sync"] }
|
||||
regex = "1"
|
||||
|
||||
@@ -471,17 +471,40 @@ pub async fn resolve_entity_info_cached(
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
/// 校验数据:检查 required 字段
|
||||
/// 校验数据:检查 required 字段 + 正则校验
|
||||
fn validate_data(data: &serde_json::Value, fields: &[PluginField]) -> AppResult<()> {
|
||||
let obj = data.as_object().ok_or_else(|| {
|
||||
AppError::Validation("data 必须是 JSON 对象".to_string())
|
||||
})?;
|
||||
|
||||
for field in fields {
|
||||
let label = field.display_name.as_deref().unwrap_or(&field.name);
|
||||
|
||||
// required 检查
|
||||
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)));
|
||||
}
|
||||
|
||||
// 正则校验
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -552,3 +575,46 @@ async fn validate_ref_entities(
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user