use evorule_tcb::JsonValue;
use serde::Serialize;
const VALID_TRANSFORM_TYPES: &[&str] = &[
"branch",
"set",
"push",
"io_request",
"noop",
"increment",
"decrement",
];
const BRANCH_REQUIRED: &[&str] = &["domain"];
const SET_REQUIRED: &[&str] = &["attr", "operation", "value"];
const PUSH_REQUIRED: &[&str] = &["instructions"];
const IO_REQUEST_REQUIRED: &[&str] = &["io_type"];
const INCREMENT_REQUIRED: &[&str] = &["attr", "delta"];
const DECREMENT_REQUIRED: &[&str] = &["attr", "delta"];
const VALID_OPERATIONS: &[&str] = &["set", "add", "sub"];
const MAX_TRANSFORM_RULES: usize = 64;
const MAX_NESTING_DEPTH: usize = 8;
const MAX_IO_PARAMS_SIZE: usize = 1024 * 10; const INFINITE_LOOP_DEPTH_THRESHOLD: usize = 5;
#[derive(Debug, Clone, Serialize)]
pub struct ValidationResult {
pub passed: bool,
pub static_validation: StaticValidation,
pub security_analysis: SecurityAnalysis,
pub summary: ValidationSummary,
}
#[derive(Debug, Clone, Serialize)]
pub struct StaticValidation {
pub checks: Vec<ValidationCheck>,
pub error_count: usize,
pub warn_count: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct ValidationCheck {
pub name: &'static str,
pub passed: bool,
pub level: &'static str,
pub message: String,
pub transform_index: i32,
}
#[derive(Debug, Clone, Serialize)]
pub struct SecurityAnalysis {
pub checks: Vec<ValidationCheck>,
pub risk_count: usize,
pub risk_level: &'static str,
}
#[derive(Debug, Clone, Serialize)]
pub struct ValidationSummary {
pub total_transforms: usize,
pub total_errors: usize,
pub total_warnings: usize,
pub total_risks: usize,
}
pub fn validate_rules(transforms: &[JsonValue]) -> ValidationResult {
let static_checks = perform_static_validation(transforms);
let static_error_count = static_checks.iter().filter(|c| c.level == "error").count();
let static_warn_count = static_checks.iter().filter(|c| c.level == "warn").count();
let security_checks = perform_security_analysis(transforms);
let risk_count = security_checks
.iter()
.filter(|c| c.level == "error" || c.level == "warn")
.count();
let total_errors = static_error_count;
let total_warnings =
static_warn_count + security_checks.iter().filter(|c| c.level == "warn").count();
let total_risks = risk_count;
let risk_level = if total_errors > 0 || total_risks > 3 {
"high"
} else if total_warnings > 0 {
"medium"
} else {
"low"
};
let passed = static_error_count == 0;
ValidationResult {
passed,
static_validation: StaticValidation {
checks: static_checks,
error_count: static_error_count,
warn_count: static_warn_count,
},
security_analysis: SecurityAnalysis {
checks: security_checks,
risk_count,
risk_level,
},
summary: ValidationSummary {
total_transforms: transforms.len(),
total_errors,
total_warnings,
total_risks,
},
}
}
fn perform_static_validation(transforms: &[JsonValue]) -> Vec<ValidationCheck> {
let mut checks = Vec::new();
checks.push(check_transform_count(transforms));
if transforms.is_empty() {
checks.push(ValidationCheck {
name: "non_empty",
passed: false,
level: "error",
message: "Transform 列表为空,至少需要一条规则".to_string(),
transform_index: -1,
});
return checks;
}
for (i, t) in transforms.iter().enumerate() {
let idx = i as i32;
checks.push(check_type_exists(t, idx));
if let Some(type_str) = t.get("type").and_then(|v| v.as_str()) {
checks.push(check_type_valid(type_str, idx));
checks.push(check_params_complete(t, type_str, idx));
}
if let Some(type_str) = t.get("type").and_then(|v| v.as_str()) {
if type_str == "branch" {
checks.extend(check_branch_nested(t, idx, 0));
}
}
}
checks
}
fn check_transform_count(transforms: &[JsonValue]) -> ValidationCheck {
let passed = transforms.len() <= MAX_TRANSFORM_RULES;
ValidationCheck {
name: "transform_count_limit",
passed,
level: if passed { "info" } else { "error" },
message: format!(
"Transform 数量: {} (上限: {}, {})",
transforms.len(),
MAX_TRANSFORM_RULES,
if passed { "合规" } else { "超出限制" }
),
transform_index: -1,
}
}
fn check_type_exists(t: &JsonValue, idx: i32) -> ValidationCheck {
let has_type = t.get("type").and_then(|v| v.as_str()).is_some();
ValidationCheck {
name: "type_exists",
passed: has_type,
level: if has_type { "info" } else { "error" },
message: if has_type {
format!("transform[{}] 的 type 字段存在", idx)
} else {
format!("transform[{}] 缺少 'type' 字段", idx)
},
transform_index: idx,
}
}
fn check_type_valid(type_str: &str, idx: i32) -> ValidationCheck {
let valid = VALID_TRANSFORM_TYPES.contains(&type_str);
ValidationCheck {
name: "type_valid",
passed: valid,
level: if valid { "info" } else { "error" },
message: if valid {
format!("transform[{}] 的 type '{}' 是合法元指令", idx, type_str)
} else {
format!(
"transform[{}] 的 type '{}' 不在白名单中 (合法值: {})",
idx,
type_str,
VALID_TRANSFORM_TYPES.join(", ")
)
},
transform_index: idx,
}
}
fn check_params_complete(t: &JsonValue, type_str: &str, idx: i32) -> ValidationCheck {
let required = match type_str {
"branch" => BRANCH_REQUIRED,
"set" => SET_REQUIRED,
"push" => PUSH_REQUIRED,
"io_request" => IO_REQUEST_REQUIRED,
"increment" => INCREMENT_REQUIRED,
"decrement" => DECREMENT_REQUIRED,
"noop" => &[], _ => {
return ValidationCheck {
name: "params_complete",
passed: true,
level: "info",
message: format!("transform[{}] 的 type '{}' 无需校验参数", idx, type_str),
transform_index: idx,
}
}
};
let mut missing: Vec<&str> = Vec::new();
let mut extra_checks: Vec<String> = Vec::new();
for param in required {
if t.get("params").and_then(|p| p.get(param)).is_none() {
missing.push(param);
}
}
if type_str == "set" && missing.is_empty() {
if let Some(op) = t
.get("params")
.and_then(|p| p.get("operation"))
.and_then(|v| v.as_str())
{
if !VALID_OPERATIONS.contains(&op) {
extra_checks.push(format!(
"operation '{}' 不在合法值中 ({})",
op,
VALID_OPERATIONS.join(", ")
));
}
}
}
let mut params_too_large = false;
if type_str == "io_request" && missing.is_empty() {
if let Some(params) = t.get("params") {
let params_str = format!("{:?}", params);
if params_str.len() > MAX_IO_PARAMS_SIZE {
extra_checks.push(format!(
"io_request 参数过大: {} 字节 (上限: {} 字节)",
params_str.len(),
MAX_IO_PARAMS_SIZE
));
params_too_large = true;
}
}
}
let passed = missing.is_empty() && extra_checks.is_empty();
let level = if passed {
"info"
} else if !missing.is_empty() || params_too_large {
"error"
} else {
"warn"
};
let mut msg_parts: Vec<String> = Vec::new();
if !missing.is_empty() {
msg_parts.push(format!("缺少必填参数: {}", missing.join(", ")));
}
msg_parts.extend(extra_checks);
ValidationCheck {
name: "params_complete",
passed,
level,
message: if msg_parts.is_empty() {
format!("transform[{}] 的 params 参数完备", idx)
} else {
format!("transform[{}]: {}", idx, msg_parts.join("; "))
},
transform_index: idx,
}
}
fn check_branch_nested(t: &JsonValue, idx: i32, depth: usize) -> Vec<ValidationCheck> {
let mut checks = Vec::new();
if depth > MAX_NESTING_DEPTH {
checks.push(ValidationCheck {
name: "nesting_depth",
passed: false,
level: "error",
message: format!("transform[{}] 的嵌套深度超过 {} 层", idx, MAX_NESTING_DEPTH),
transform_index: idx,
});
return checks;
}
if let Some(on_true) = t
.get("params")
.and_then(|p| p.get("on_true"))
.and_then(|v| v.as_array())
{
for child in on_true {
if let Some(child_type) = child.get("type").and_then(|v| v.as_str()) {
if child_type == "branch" {
checks.extend(check_branch_nested(child, idx, depth + 1));
}
}
}
}
if let Some(on_false) = t
.get("params")
.and_then(|p| p.get("on_false"))
.and_then(|v| v.as_array())
{
for child in on_false {
if let Some(child_type) = child.get("type").and_then(|v| v.as_str()) {
if child_type == "branch" {
checks.extend(check_branch_nested(child, idx, depth + 1));
}
}
}
}
checks
}
fn perform_security_analysis(transforms: &[JsonValue]) -> Vec<ValidationCheck> {
vec![
check_infinite_loop_risk(transforms),
check_recursive_nesting(transforms),
check_unbounded_io(transforms),
check_payload_growth(transforms),
check_self_reference(transforms),
]
}
fn check_infinite_loop_risk(transforms: &[JsonValue]) -> ValidationCheck {
let mut has_while_loop = false;
let mut has_state_change = false;
for t in transforms {
if let Some(type_str) = t.get("type").and_then(|v| v.as_str()) {
if type_str == "branch" {
if let Some(domain) = t.get("params").and_then(|p| p.get("domain")) {
if let Some(inner_type) = domain.get("type").and_then(|v| v.as_str()) {
if inner_type == "instruction" {
if let Some(inst_type) =
domain.get("instruction_type").and_then(|v| v.as_str())
{
if evorule_reactor::ControlFlowType::parse(inst_type)
== Some(evorule_reactor::ControlFlowType::WhileLoop)
{
has_while_loop = true;
}
}
}
}
}
}
if matches!(type_str, "set" | "increment" | "decrement") {
has_state_change = true;
}
}
}
let risk = has_while_loop && !has_state_change;
ValidationCheck {
name: "infinite_loop",
passed: !risk,
level: if risk { "warn" } else { "info" },
message: if risk {
"检测到 while_loop 但未找到状态变更指令 (set/increment/decrement),可能导致无限循环"
.to_string()
} else if has_while_loop {
"while_loop 存在配套的状态变更指令,循环可终止".to_string()
} else {
"未检测到 while_loop 指令".to_string()
},
transform_index: -1,
}
}
fn check_recursive_nesting(transforms: &[JsonValue]) -> ValidationCheck {
let mut max_depth = 0usize;
for t in transforms.iter() {
if let Some(type_str) = t.get("type").and_then(|v| v.as_str()) {
if type_str == "branch" {
let depth = measure_nesting_depth(t, 0);
max_depth = max_depth.max(depth);
}
}
}
let risk = max_depth >= INFINITE_LOOP_DEPTH_THRESHOLD;
ValidationCheck {
name: "recursive_nesting",
passed: !risk,
level: if risk { "warn" } else { "info" },
message: format!(
"最大嵌套深度: {} 层{}",
max_depth,
if risk {
format!(" (超过阈值 {},建议拆分)", INFINITE_LOOP_DEPTH_THRESHOLD)
} else {
String::new()
}
),
transform_index: -1,
}
}
fn measure_nesting_depth(t: &JsonValue, depth: usize) -> usize {
let mut max_child_depth = depth;
for branch_key in &["on_true", "on_false"] {
if let Some(children) = t
.get("params")
.and_then(|p| p.get(branch_key))
.and_then(|v| v.as_array())
{
for child in children {
if let Some(child_type) = child.get("type").and_then(|v| v.as_str()) {
if child_type == "branch" {
let child_depth = measure_nesting_depth(child, depth + 1);
max_child_depth = max_child_depth.max(child_depth);
}
}
}
}
}
max_child_depth
}
fn check_unbounded_io(transforms: &[JsonValue]) -> ValidationCheck {
let mut unbounded_io_count = 0usize;
for t in transforms {
if let Some(type_str) = t.get("type").and_then(|v| v.as_str()) {
if type_str == "branch" {
unbounded_io_count += count_io_in_loop_body(t);
}
}
}
let risk = unbounded_io_count > 0;
ValidationCheck {
name: "unbounded_io",
passed: !risk,
level: if risk { "warn" } else { "info" },
message: if risk {
format!(
"检测到 {} 处 io_request 可能在循环体内执行,可能导致无限制 I/O 调用",
unbounded_io_count
)
} else {
"未检测到循环体内的 io_request".to_string()
},
transform_index: -1,
}
}
fn count_io_in_loop_body(t: &JsonValue) -> usize {
let mut count = 0usize;
for branch_key in &["on_true", "on_false"] {
if let Some(children) = t
.get("params")
.and_then(|p| p.get(branch_key))
.and_then(|v| v.as_array())
{
for child in children {
if let Some(child_type) = child.get("type").and_then(|v| v.as_str()) {
match child_type {
"io_request" => count += 1,
"branch" => count += count_io_in_loop_body(child),
_ => {}
}
}
}
}
}
count
}
fn check_payload_growth(transforms: &[JsonValue]) -> ValidationCheck {
let mut push_count = 0usize;
let mut has_cleanup = false;
for t in transforms {
if let Some(type_str) = t.get("type").and_then(|v| v.as_str()) {
if type_str == "push" {
push_count += 1;
}
if type_str == "set" {
if let Some(value) = t.get("params").and_then(|p| p.get("value")) {
if value.is_array() || value.is_null() {
has_cleanup = true;
}
}
}
}
}
let risk = push_count > 3 && !has_cleanup;
ValidationCheck {
name: "payload_growth",
passed: !risk,
level: if risk { "warn" } else { "info" },
message: if risk {
format!(
"检测到 {} 处 push 指令但未找到清理操作,可能导致 payload 无界增长",
push_count
)
} else {
format!(
"push 指令 {} 处{}",
push_count,
if has_cleanup {
",有配套清理操作"
} else {
""
}
)
},
transform_index: -1,
}
}
fn check_self_reference(transforms: &[JsonValue]) -> ValidationCheck {
let mut self_refs: Vec<String> = Vec::new();
for (i, t) in transforms.iter().enumerate() {
if let Some(type_str) = t.get("type").and_then(|v| v.as_str()) {
if type_str == "branch" {
if let Some(domain) = t.get("params").and_then(|p| p.get("domain")) {
if let Some(inner_type) = domain.get("type").and_then(|v| v.as_str()) {
if inner_type == "instruction" {
if let Some(inst_type) =
domain.get("instruction_type").and_then(|v| v.as_str())
{
if let Some(on_true) = t
.get("params")
.and_then(|p| p.get("on_true"))
.and_then(|v| v.as_array())
{
for child in on_true {
if let Some(child_type) =
child.get("type").and_then(|v| v.as_str())
{
if child_type == inst_type || child_type == "branch" {
self_refs.push(format!(
"transform[{}] 匹配 '{}' 但体内包含同类型指令",
i, inst_type
));
}
}
}
}
}
}
}
}
}
}
}
let risk = !self_refs.is_empty();
ValidationCheck {
name: "self_reference",
passed: !risk,
level: if risk { "warn" } else { "info" },
message: if risk {
format!("检测到自引用风险: {}", self_refs.join("; "))
} else {
"未检测到自引用".to_string()
},
transform_index: -1,
}
}
pub fn validate_rules_from_json(json_str: &str) -> Result<ValidationResult, String> {
let parsed: serde_json::Value =
serde_json::from_str(json_str).map_err(|e| format!("JSON 解析失败: {}", e))?;
let transforms = extract_transforms(&parsed)?;
let tcb_transforms: Vec<JsonValue> = transforms
.iter()
.map(evorule_reactor::serde_to_tcb)
.collect();
Ok(validate_rules(&tcb_transforms))
}
fn extract_transforms(json: &serde_json::Value) -> Result<Vec<serde_json::Value>, String> {
match json {
serde_json::Value::Object(map) => {
if let Some(arr) = map.get("transform").and_then(|v| v.as_array()) {
Ok(arr.clone())
} else if let Some(arr) = map.get("transforms").and_then(|v| v.as_array()) {
Ok(arr.clone())
} else {
Ok(vec![json.clone()])
}
}
serde_json::Value::Array(arr) => Ok(arr.clone()),
_ => Err("JSON 必须是对象或数组".to_string()),
}
}
pub fn validation_result_to_json(result: &ValidationResult) -> String {
serde_json::to_string_pretty(result).unwrap_or_else(|_| "{}".to_string())
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
#[test]
fn test_validate_valid_rules() {
let transforms = vec![
json_to_tcb(r#"{"type":"noop"}"#),
json_to_tcb(r#"{"type":"set","params":{"attr":"x","operation":"set","value":0}}"#),
json_to_tcb(
r#"{"type":"branch","params":{"domain":{"type":"instruction","instruction_type":"increment"},"on_true":[{"type":"set","params":{"attr":"x","operation":"add","delta":1}}]}}"#,
),
];
let result = validate_rules(&transforms);
assert!(result.passed, "Valid rules should pass");
assert_eq!(result.static_validation.error_count, 0);
}
#[test]
fn test_validate_empty_rules() {
let transforms: Vec<JsonValue> = vec![];
let result = validate_rules(&transforms);
assert!(!result.passed, "Empty rules should fail");
assert!(result.static_validation.error_count > 0);
}
#[test]
fn test_validate_unknown_type() {
let transforms = vec![json_to_tcb(r#"{"type":"unknown_type"}"#)];
let result = validate_rules(&transforms);
assert!(!result.passed, "Unknown type should fail");
assert!(result.static_validation.error_count >= 1);
}
#[test]
fn test_validate_missing_type() {
let transforms = vec![json_to_tcb(r#"{"params":{"attr":"x"}}"#)];
let result = validate_rules(&transforms);
assert!(!result.passed, "Missing type should fail");
}
#[test]
fn test_validate_missing_params() {
let transforms = vec![json_to_tcb(r#"{"type":"set"}"#)];
let result = validate_rules(&transforms);
assert!(!result.passed, "Missing params should fail");
assert!(result.static_validation.error_count >= 1);
}
#[test]
fn test_validate_invalid_operation() {
let transforms = vec![json_to_tcb(
r#"{"type":"set","params":{"attr":"x","operation":"invalid_op","value":0}}"#,
)];
let result = validate_rules(&transforms);
assert!(
result.passed,
"Invalid operation is warn-level, not blocking"
);
let params_check = result
.static_validation
.checks
.iter()
.find(|c| c.name == "params_complete");
assert!(params_check.is_some());
assert_eq!(params_check.unwrap().level, "warn");
}
#[test]
fn test_security_infinite_loop_detection() {
let transforms = vec![json_to_tcb(
r#"{"type":"branch","params":{"domain":{"type":"instruction","instruction_type":"while_loop"},"on_true":[{"type":"noop"}]}}"#,
)];
let result = validate_rules(&transforms);
let infinite_loop = result
.security_analysis
.checks
.iter()
.find(|c| c.name == "infinite_loop");
assert!(infinite_loop.is_some());
assert!(!infinite_loop.unwrap().passed);
}
#[test]
fn test_security_unbounded_io() {
let transforms = vec![json_to_tcb(
r#"{"type":"branch","params":{"domain":{"type":"instruction","instruction_type":"while_loop"},"on_true":[{"type":"io_request","params":{"io_type":"http_get"}}]}}"#,
)];
let result = validate_rules(&transforms);
let unbounded_io = result
.security_analysis
.checks
.iter()
.find(|c| c.name == "unbounded_io");
assert!(unbounded_io.is_some());
assert!(!unbounded_io.unwrap().passed);
}
#[test]
fn test_security_payload_growth() {
let transforms = vec![
json_to_tcb(r#"{"type":"push","params":{"instructions":[]}}"#),
json_to_tcb(r#"{"type":"push","params":{"instructions":[]}}"#),
json_to_tcb(r#"{"type":"push","params":{"instructions":[]}}"#),
json_to_tcb(r#"{"type":"push","params":{"instructions":[]}}"#),
];
let result = validate_rules(&transforms);
let payload_growth = result
.security_analysis
.checks
.iter()
.find(|c| c.name == "payload_growth");
assert!(payload_growth.is_some());
assert!(!payload_growth.unwrap().passed);
}
#[test]
fn test_security_cleanup_detected() {
let transforms = vec![
json_to_tcb(r#"{"type":"push","params":{"instructions":[]}}"#),
json_to_tcb(r#"{"type":"push","params":{"instructions":[]}}"#),
json_to_tcb(r#"{"type":"push","params":{"instructions":[]}}"#),
json_to_tcb(r#"{"type":"push","params":{"instructions":[]}}"#),
json_to_tcb(
r#"{"type":"set","params":{"attr":"queue","operation":"set","value":null}}"#,
),
];
let result = validate_rules(&transforms);
let payload_growth = result
.security_analysis
.checks
.iter()
.find(|c| c.name == "payload_growth");
assert!(payload_growth.is_some());
assert!(payload_growth.unwrap().passed);
}
fn json_to_tcb(json_str: &str) -> JsonValue {
let v: serde_json::Value = serde_json::from_str(json_str).unwrap();
evorule_reactor::serde_to_tcb(&v)
}
#[test]
fn test_validate_rules_from_json() {
let json = r#"{"transform":[{"type":"noop"},{"type":"set","params":{"attr":"x","operation":"set","value":0}}]}"#;
let result = validate_rules_from_json(json).unwrap();
assert!(result.passed);
}
#[test]
fn test_validate_rules_from_json_array() {
let json =
r#"[{"type":"noop"},{"type":"set","params":{"attr":"x","operation":"set","value":0}}]"#;
let result = validate_rules_from_json(json).unwrap();
assert!(result.passed);
}
#[test]
fn test_validate_rules_from_json_invalid() {
let result = validate_rules_from_json("not json");
assert!(result.is_err());
}
#[test]
fn test_transform_count_limit() {
let mut transforms = Vec::new();
for _ in 0..65 {
transforms.push(json_to_tcb(r#"{"type":"noop"}"#));
}
let result = validate_rules(&transforms);
let count_check = result
.static_validation
.checks
.iter()
.find(|c| c.name == "transform_count_limit");
assert!(count_check.is_some());
assert!(!count_check.unwrap().passed);
}
}