From 210c12a74e68f7df764b288a575d6a0aab196f11 Mon Sep 17 00:00:00 2001 From: Tunglies Date: Sat, 18 Oct 2025 10:57:57 +0800 Subject: [PATCH] feat: implement CoreConfigValidator for configuration validation and enhance logging types (#5112) --- src-tauri/src/cmd/clash.rs | 7 +- src-tauri/src/cmd/save_profile.rs | 12 +- src-tauri/src/cmd/validate.rs | 11 +- src-tauri/src/config/config.rs | 4 +- src-tauri/src/core/core.rs | 290 +----------------------- src-tauri/src/core/mod.rs | 1 + src-tauri/src/core/validate.rs | 358 ++++++++++++++++++++++++++++++ src-tauri/src/utils/logging.rs | 2 + 8 files changed, 377 insertions(+), 308 deletions(-) create mode 100644 src-tauri/src/core/validate.rs diff --git a/src-tauri/src/cmd/clash.rs b/src-tauri/src/cmd/clash.rs index dfb2f732..91b4eab7 100644 --- a/src-tauri/src/cmd/clash.rs +++ b/src-tauri/src/cmd/clash.rs @@ -4,7 +4,7 @@ use super::CmdResult; use crate::{ cmd::StringifyErr, config::Config, - core::{CoreManager, handle}, + core::{CoreManager, handle, validate::CoreConfigValidator}, }; use crate::{config::*, feat, logging, utils::logging::Type, wrap_err}; use compact_str::CompactString; @@ -257,7 +257,7 @@ pub async fn get_dns_config_content() -> CmdResult { /// 验证DNS配置文件 #[tauri::command] pub async fn validate_dns_config() -> CmdResult<(bool, String)> { - use crate::{core::CoreManager, utils::dirs}; + use crate::utils::dirs; let app_dir = dirs::app_home_dir().stringify_err()?; let dns_path = app_dir.join("dns_config.yaml"); @@ -267,8 +267,7 @@ pub async fn validate_dns_config() -> CmdResult<(bool, String)> { return Ok((false, "DNS config file not found".into())); } - CoreManager::global() - .validate_config_file(dns_path_str, None) + CoreConfigValidator::validate_config_file(dns_path_str, None) .await .stringify_err() } diff --git a/src-tauri/src/cmd/save_profile.rs b/src-tauri/src/cmd/save_profile.rs index 7a1b39cd..92edda9b 100644 --- a/src-tauri/src/cmd/save_profile.rs +++ b/src-tauri/src/cmd/save_profile.rs @@ -1,7 +1,7 @@ use super::CmdResult; use crate::{ config::*, - core::*, + core::{validate::CoreConfigValidator, *}, logging, utils::{dirs, logging::Type}, wrap_err, @@ -48,10 +48,7 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR Type::Config, "[cmd配置save] 检测到merge文件,只进行语法验证" ); - match CoreManager::global() - .validate_config_file(&file_path_str, Some(true)) - .await - { + match CoreConfigValidator::validate_config_file(&file_path_str, Some(true)).await { Ok((true, _)) => { logging!(info, Type::Config, "[cmd配置save] merge文件语法验证通过"); // 成功后尝试更新整体配置 @@ -95,10 +92,7 @@ pub async fn save_profile_file(index: String, file_data: Option) -> CmdR } // 非merge文件使用完整验证流程 - match CoreManager::global() - .validate_config_file(&file_path_str, None) - .await - { + match CoreConfigValidator::validate_config_file(&file_path_str, None).await { Ok((true, _)) => { logging!(info, Type::Config, "[cmd配置save] 验证成功"); Ok(()) diff --git a/src-tauri/src/cmd/validate.rs b/src-tauri/src/cmd/validate.rs index d4ccfefa..23d93b42 100644 --- a/src-tauri/src/cmd/validate.rs +++ b/src-tauri/src/cmd/validate.rs @@ -1,5 +1,9 @@ use super::CmdResult; -use crate::{core::*, logging, utils::logging::Type}; +use crate::{ + core::{validate::CoreConfigValidator, *}, + logging, + utils::logging::Type, +}; /// 发送脚本验证通知消息 #[tauri::command] @@ -38,10 +42,7 @@ pub fn handle_script_validation_notice(result: &(bool, String), file_type: &str) pub async fn validate_script_file(file_path: String) -> CmdResult { logging!(info, Type::Config, "验证脚本文件: {}", file_path); - match CoreManager::global() - .validate_config_file(&file_path, None) - .await - { + match CoreConfigValidator::validate_config_file(&file_path, None).await { Ok(result) => { handle_script_validation_notice(&result, "脚本文件"); Ok(result.0) // 返回验证结果布尔值 diff --git a/src-tauri/src/config/config.rs b/src-tauri/src/config/config.rs index c6be0bae..52bbaabf 100644 --- a/src-tauri/src/config/config.rs +++ b/src-tauri/src/config/config.rs @@ -1,7 +1,7 @@ use super::{IClashTemp, IProfiles, IRuntime, IVerge}; use crate::{ config::{PrfItem, profiles_append_item_safe}, - core::{CoreManager, handle}, + core::{CoreManager, handle, validate::CoreConfigValidator}, enhance, logging, utils::{Draft, dirs, help, logging::Type}, }; @@ -87,7 +87,7 @@ impl Config { // 验证配置文件 logging!(info, Type::Config, "开始验证配置"); - match CoreManager::global().validate_config().await { + match CoreConfigValidator::global().validate_config().await { Ok((is_valid, error_msg)) => { if !is_valid { logging!( diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs index 0a986f50..9f6aa03d 100644 --- a/src-tauri/src/core/core.rs +++ b/src-tauri/src/core/core.rs @@ -1,5 +1,6 @@ use crate::AsyncHandler; use crate::core::logger::ClashLogger; +use crate::core::validate::CoreConfigValidator; use crate::process::CommandChildGuard; use crate::utils::init::sidecar_writer; use crate::utils::logging::{SharedWriter, write_sidecar_log}; @@ -67,84 +68,6 @@ impl fmt::Display for RunningMode { use crate::config::IVerge; impl CoreManager { - /// 检查文件是否为脚本文件 - fn is_script_file(&self, path: &str) -> Result { - // 1. 先通过扩展名快速判断 - if path.ends_with(".yaml") || path.ends_with(".yml") { - return Ok(false); // YAML文件不是脚本文件 - } else if path.ends_with(".js") { - return Ok(true); // JS文件是脚本文件 - } - - // 2. 读取文件内容 - let content = match std::fs::read_to_string(path) { - Ok(content) => content, - Err(err) => { - logging!( - warn, - Type::Config, - "无法读取文件以检测类型: {}, 错误: {}", - path, - err - ); - return Err(anyhow::anyhow!( - "Failed to read file to detect type: {}", - err - )); - } - }; - - // 3. 检查是否存在明显的YAML特征 - let has_yaml_features = content.contains(": ") - || content.contains("#") - || content.contains("---") - || content.lines().any(|line| line.trim().starts_with("- ")); - - // 4. 检查是否存在明显的JS特征 - let has_js_features = content.contains("function ") - || content.contains("const ") - || content.contains("let ") - || content.contains("var ") - || content.contains("//") - || content.contains("/*") - || content.contains("*/") - || content.contains("export ") - || content.contains("import "); - - // 5. 决策逻辑 - if has_yaml_features && !has_js_features { - // 只有YAML特征,没有JS特征 - return Ok(false); - } else if has_js_features && !has_yaml_features { - // 只有JS特征,没有YAML特征 - return Ok(true); - } else if has_yaml_features && has_js_features { - // 两种特征都有,需要更精细判断 - // 优先检查是否有明确的JS结构特征 - if content.contains("function main") - || content.contains("module.exports") - || content.contains("export default") - { - return Ok(true); - } - - // 检查冒号后是否有空格(YAML的典型特征) - let yaml_pattern_count = content.lines().filter(|line| line.contains(": ")).count(); - - if yaml_pattern_count > 2 { - return Ok(false); // 多个键值对格式,更可能是YAML - } - } - - // 默认情况:无法确定时,假设为非脚本文件(更安全) - logging!( - debug, - Type::Config, - "无法确定文件类型,默认当作YAML处理: {}", - path - ); - Ok(false) - } /// 使用默认配置 pub async fn use_default_config(&self, msg_type: &str, msg_content: &str) -> Result<()> { let runtime_path = dirs::app_home_dir()?.join(RUNTIME_CONFIG); @@ -161,216 +84,7 @@ impl CoreManager { handle::Handle::notice_message(msg_type, msg_content); Ok(()) } - /// 验证运行时配置 - pub async fn validate_config(&self) -> Result<(bool, String)> { - logging!(info, Type::Config, "生成临时配置文件用于验证"); - let config_path = Config::generate_file(ConfigType::Check).await?; - let config_path = dirs::path_to_str(&config_path)?; - self.validate_config_internal(config_path).await - } - /// 验证指定的配置文件 - pub async fn validate_config_file( - &self, - config_path: &str, - is_merge_file: Option, - ) -> Result<(bool, String)> { - // 检查程序是否正在退出,如果是则跳过验证 - if handle::Handle::global().is_exiting() { - logging!(info, Type::Core, "应用正在退出,跳过验证"); - return Ok((true, String::new())); - } - // 检查文件是否存在 - if !std::path::Path::new(config_path).exists() { - let error_msg = format!("File not found: {config_path}"); - //handle::Handle::notice_message("config_validate::file_not_found", &error_msg); - return Ok((false, error_msg)); - } - - // 如果是合并文件且不是强制验证,执行语法检查但不进行完整验证 - if is_merge_file.unwrap_or(false) { - logging!( - info, - Type::Config, - "检测到Merge文件,仅进行语法检查: {}", - config_path - ); - return self.validate_file_syntax(config_path); - } - - // 检查是否为脚本文件 - let is_script = if config_path.ends_with(".js") { - true - } else { - match self.is_script_file(config_path) { - Ok(result) => result, - Err(err) => { - // 如果无法确定文件类型,尝试使用Clash内核验证 - logging!( - warn, - Type::Config, - "无法确定文件类型: {}, 错误: {}", - config_path, - err - ); - return self.validate_config_internal(config_path).await; - } - } - }; - - if is_script { - logging!( - info, - Type::Config, - "检测到脚本文件,使用JavaScript验证: {}", - config_path - ); - return self.validate_script_file(config_path); - } - - // 对YAML配置文件使用Clash内核验证 - logging!( - info, - Type::Config, - "使用Clash内核验证配置文件: {}", - config_path - ); - self.validate_config_internal(config_path).await - } - /// 内部验证配置文件的实现 - async fn validate_config_internal(&self, config_path: &str) -> Result<(bool, String)> { - // 检查程序是否正在退出,如果是则跳过验证 - if handle::Handle::global().is_exiting() { - logging!(info, Type::Core, "应用正在退出,跳过验证"); - return Ok((true, String::new())); - } - - logging!(info, Type::Config, "开始验证配置文件: {}", config_path); - - let clash_core = Config::verge().await.latest_ref().get_valid_clash_core(); - logging!(info, Type::Config, "使用内核: {}", clash_core); - - let app_handle = handle::Handle::app_handle(); - let app_dir = dirs::app_home_dir()?; - let app_dir_str = dirs::path_to_str(&app_dir)?; - logging!(info, Type::Config, "验证目录: {}", app_dir_str); - - // 使用子进程运行clash验证配置 - let output = app_handle - .shell() - .sidecar(clash_core)? - .args(["-t", "-d", app_dir_str, "-f", config_path]) - .output() - .await?; - - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - - // 检查进程退出状态和错误输出 - let error_keywords = ["FATA", "fatal", "Parse config error", "level=fatal"]; - let has_error = - !output.status.success() || error_keywords.iter().any(|&kw| stderr.contains(kw)); - - logging!(info, Type::Config, "-------- 验证结果 --------"); - - if !stderr.is_empty() { - logging!(info, Type::Config, "stderr输出:\n{}", stderr); - } - - if has_error { - logging!(info, Type::Config, "发现错误,开始处理错误信息"); - let error_msg = if !stdout.is_empty() { - stdout.into() - } else if !stderr.is_empty() { - stderr.into() - } else if let Some(code) = output.status.code() { - format!("验证进程异常退出,退出码: {code}") - } else { - "验证进程被终止".into() - }; - - logging!(info, Type::Config, "-------- 验证结束 --------"); - Ok((false, error_msg)) // 返回错误消息给调用者处理 - } else { - logging!(info, Type::Config, "验证成功"); - logging!(info, Type::Config, "-------- 验证结束 --------"); - Ok((true, String::new())) - } - } - /// 只进行文件语法检查,不进行完整验证 - fn validate_file_syntax(&self, config_path: &str) -> Result<(bool, String)> { - logging!(info, Type::Config, "开始检查文件: {}", config_path); - - // 读取文件内容 - let content = match std::fs::read_to_string(config_path) { - Ok(content) => content, - Err(err) => { - let error_msg = format!("Failed to read file: {err}"); - logging!(error, Type::Config, "无法读取文件: {}", error_msg); - return Ok((false, error_msg)); - } - }; - // 对YAML文件尝试解析,只检查语法正确性 - logging!(info, Type::Config, "进行YAML语法检查"); - match serde_yaml_ng::from_str::(&content) { - Ok(_) => { - logging!(info, Type::Config, "YAML语法检查通过"); - Ok((true, String::new())) - } - Err(err) => { - // 使用标准化的前缀,以便错误处理函数能正确识别 - let error_msg = format!("YAML syntax error: {err}"); - logging!(error, Type::Config, "YAML语法错误: {}", error_msg); - Ok((false, error_msg)) - } - } - } - /// 验证脚本文件语法 - fn validate_script_file(&self, path: &str) -> Result<(bool, String)> { - // 读取脚本内容 - let content = match std::fs::read_to_string(path) { - Ok(content) => content, - Err(err) => { - let error_msg = format!("Failed to read script file: {err}"); - logging!(warn, Type::Config, "脚本语法错误: {}", err); - //handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg); - return Ok((false, error_msg)); - } - }; - - logging!(debug, Type::Config, "验证脚本文件: {}", path); - - // 使用boa引擎进行基本语法检查 - use boa_engine::{Context, Source}; - - let mut context = Context::default(); - let result = context.eval(Source::from_bytes(&content)); - - match result { - Ok(_) => { - logging!(debug, Type::Config, "脚本语法验证通过: {}", path); - - // 检查脚本是否包含main函数 - if !content.contains("function main") - && !content.contains("const main") - && !content.contains("let main") - { - let error_msg = "Script must contain a main function"; - logging!(warn, Type::Config, "脚本缺少main函数: {}", path); - //handle::Handle::notice_message("config_validate::script_missing_main", error_msg); - return Ok((false, error_msg.into())); - } - - Ok((true, String::new())) - } - Err(err) => { - let error_msg = format!("Script syntax error: {err}"); - logging!(warn, Type::Config, "脚本语法错误: {}", err); - //handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg); - Ok((false, error_msg)) - } - } - } /// 更新proxies等配置 pub async fn update_config(&self) -> Result<(bool, String)> { // 检查程序是否正在退出,如果是则跳过完整验证流程 @@ -384,7 +98,7 @@ impl CoreManager { Config::generate().await?; // 2. 验证配置 - match self.validate_config().await { + match CoreConfigValidator::global().validate_config().await { Ok((true, _)) => { // 4. 验证通过后,生成正式的运行时配置 logging!(info, Type::Config, "配置验证通过, 生成运行时配置"); diff --git a/src-tauri/src/core/mod.rs b/src-tauri/src/core/mod.rs index 2a01fe23..03e44d57 100644 --- a/src-tauri/src/core/mod.rs +++ b/src-tauri/src/core/mod.rs @@ -10,6 +10,7 @@ pub mod service; pub mod sysopt; pub mod timer; pub mod tray; +pub mod validate; pub mod win_uwp; pub use self::{core::*, event_driven_proxy::EventDrivenProxyManager, timer::Timer}; diff --git a/src-tauri/src/core/validate.rs b/src-tauri/src/core/validate.rs new file mode 100644 index 00000000..a30f53a3 --- /dev/null +++ b/src-tauri/src/core/validate.rs @@ -0,0 +1,358 @@ +use anyhow::Result; +use std::path::Path; +use std::sync::Arc; +use tauri_plugin_shell::ShellExt; +use tokio::sync::Mutex; + +use crate::config::{Config, ConfigType}; +use crate::core::handle; +use crate::singleton_lazy; +use crate::utils::dirs; +use crate::{logging, utils::logging::Type}; + +// pub enum ValidationResult { +// Valid, +// Invalid(String), +// } + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum ValidationProcessStatus { + Ongoing, + Completed, +} + +pub struct CoreConfigValidator { + // inner: Vec, + // result: ValidationResult, + process_status: Arc>, +} + +impl CoreConfigValidator { + pub fn new() -> Self { + CoreConfigValidator { + process_status: Arc::new(Mutex::new(ValidationProcessStatus::Completed)), + } + } + + /// 检查文件是否为脚本文件 + fn is_script_file

(path: P) -> Result + where + P: AsRef + std::fmt::Display, + { + // 1. 先通过扩展名快速判断 + if has_ext(&path, "yaml") || has_ext(&path, "yml") { + return Ok(false); // YAML文件不是脚本文件 + } else if has_ext(&path, "js") { + return Ok(true); // JS文件是脚本文件 + } + + // 2. 读取文件内容 + let content = match std::fs::read_to_string(&path) { + Ok(content) => content, + Err(err) => { + logging!( + warn, + Type::Validate, + "无法读取文件以检测类型: {}, 错误: {}", + path, + err + ); + return Err(anyhow::anyhow!( + "Failed to read file to detect type: {}", + err + )); + } + }; + + // 3. 检查是否存在明显的YAML特征 + let has_yaml_features = content.contains(": ") + || content.contains("#") + || content.contains("---") + || content.lines().any(|line| line.trim().starts_with("- ")); + + // 4. 检查是否存在明显的JS特征 + let has_js_features = content.contains("function ") + || content.contains("const ") + || content.contains("let ") + || content.contains("var ") + || content.contains("//") + || content.contains("/*") + || content.contains("*/") + || content.contains("export ") + || content.contains("import "); + + // 5. 决策逻辑 + if has_yaml_features && !has_js_features { + // 只有YAML特征,没有JS特征 + return Ok(false); + } else if has_js_features && !has_yaml_features { + // 只有JS特征,没有YAML特征 + return Ok(true); + } else if has_yaml_features && has_js_features { + // 两种特征都有,需要更精细判断 + // 优先检查是否有明确的JS结构特征 + if content.contains("function main") + || content.contains("module.exports") + || content.contains("export default") + { + return Ok(true); + } + + // 检查冒号后是否有空格(YAML的典型特征) + let yaml_pattern_count = content.lines().filter(|line| line.contains(": ")).count(); + + if yaml_pattern_count > 2 { + return Ok(false); // 多个键值对格式,更可能是YAML + } + } + + // 默认情况:无法确定时,假设为非脚本文件(更安全) + logging!( + debug, + Type::Validate, + "无法确定文件类型,默认当作YAML处理: {}", + path + ); + Ok(false) + } + + /// 只进行文件语法检查,不进行完整验证 + fn validate_file_syntax(config_path: &str) -> Result<(bool, String)> { + logging!(info, Type::Validate, "开始检查文件: {}", config_path); + + // 读取文件内容 + let content = match std::fs::read_to_string(config_path) { + Ok(content) => content, + Err(err) => { + let error_msg = format!("Failed to read file: {err}"); + logging!(error, Type::Validate, "无法读取文件: {}", error_msg); + return Ok((false, error_msg)); + } + }; + // 对YAML文件尝试解析,只检查语法正确性 + logging!(info, Type::Validate, "进行YAML语法检查"); + match serde_yaml_ng::from_str::(&content) { + Ok(_) => { + logging!(info, Type::Validate, "YAML语法检查通过"); + Ok((true, String::new())) + } + Err(err) => { + // 使用标准化的前缀,以便错误处理函数能正确识别 + let error_msg = format!("YAML syntax error: {err}"); + logging!(error, Type::Validate, "YAML语法错误: {}", error_msg); + Ok((false, error_msg)) + } + } + } + + /// 验证脚本文件语法 + fn validate_script_file(path: &str) -> Result<(bool, String)> { + // 读取脚本内容 + let content = match std::fs::read_to_string(path) { + Ok(content) => content, + Err(err) => { + let error_msg = format!("Failed to read script file: {err}"); + logging!(warn, Type::Validate, "脚本语法错误: {}", err); + //handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg); + return Ok((false, error_msg)); + } + }; + + logging!(debug, Type::Validate, "验证脚本文件: {}", path); + + // 使用boa引擎进行基本语法检查 + use boa_engine::{Context, Source}; + + let mut context = Context::default(); + let result = context.eval(Source::from_bytes(&content)); + + match result { + Ok(_) => { + logging!(debug, Type::Validate, "脚本语法验证通过: {}", path); + + // 检查脚本是否包含main函数 + if !content.contains("function main") + && !content.contains("const main") + && !content.contains("let main") + { + let error_msg = "Script must contain a main function"; + logging!(warn, Type::Validate, "脚本缺少main函数: {}", path); + //handle::Handle::notice_message("config_validate::script_missing_main", error_msg); + return Ok((false, error_msg.into())); + } + + Ok((true, String::new())) + } + Err(err) => { + let error_msg = format!("Script syntax error: {err}"); + logging!(warn, Type::Validate, "脚本语法错误: {}", err); + //handle::Handle::notice_message("config_validate::script_syntax_error", &error_msg); + Ok((false, error_msg)) + } + } + } + + /// 验证指定的配置文件 + pub async fn validate_config_file( + config_path: &str, + is_merge_file: Option, + ) -> Result<(bool, String)> { + // 检查程序是否正在退出,如果是则跳过验证 + if handle::Handle::global().is_exiting() { + logging!(info, Type::Core, "应用正在退出,跳过验证"); + return Ok((true, String::new())); + } + + // 检查文件是否存在 + if !std::path::Path::new(config_path).exists() { + let error_msg = format!("File not found: {config_path}"); + //handle::Handle::notice_message("config_validate::file_not_found", &error_msg); + return Ok((false, error_msg)); + } + + // 如果是合并文件且不是强制验证,执行语法检查但不进行完整验证 + if is_merge_file.unwrap_or(false) { + logging!( + info, + Type::Validate, + "检测到Merge文件,仅进行语法检查: {}", + config_path + ); + return Self::validate_file_syntax(config_path); + } + + // 检查是否为脚本文件 + let is_script = if config_path.ends_with(".js") { + true + } else { + match Self::is_script_file(config_path) { + Ok(result) => result, + Err(err) => { + // 如果无法确定文件类型,尝试使用Clash内核验证 + logging!( + warn, + Type::Validate, + "无法确定文件类型: {}, 错误: {}", + config_path, + err + ); + return Self::validate_config_internal(config_path).await; + } + } + }; + + if is_script { + logging!( + info, + Type::Validate, + "检测到脚本文件,使用JavaScript验证: {}", + config_path + ); + return Self::validate_script_file(config_path); + } + + // 对YAML配置文件使用Clash内核验证 + logging!( + info, + Type::Validate, + "使用Clash内核验证配置文件: {}", + config_path + ); + Self::validate_config_internal(config_path).await + } + + /// 内部验证配置文件的实现 + async fn validate_config_internal(config_path: &str) -> Result<(bool, String)> { + // 检查程序是否正在退出,如果是则跳过验证 + if handle::Handle::global().is_exiting() { + logging!(info, Type::Validate, "应用正在退出,跳过验证"); + return Ok((true, String::new())); + } + + logging!(info, Type::Validate, "开始验证配置文件: {}", config_path); + + let clash_core = Config::verge().await.latest_ref().get_valid_clash_core(); + logging!(info, Type::Validate, "使用内核: {}", clash_core); + + let app_handle = handle::Handle::app_handle(); + let app_dir = dirs::app_home_dir()?; + let app_dir_str = dirs::path_to_str(&app_dir)?; + logging!(info, Type::Validate, "验证目录: {}", app_dir_str); + + // 使用子进程运行clash验证配置 + let output = app_handle + .shell() + .sidecar(clash_core)? + .args(["-t", "-d", app_dir_str, "-f", config_path]) + .output() + .await?; + + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + + // 检查进程退出状态和错误输出 + let error_keywords = ["FATA", "fatal", "Parse config error", "level=fatal"]; + let has_error = + !output.status.success() || error_keywords.iter().any(|&kw| stderr.contains(kw)); + + logging!(info, Type::Validate, "-------- 验证结果 --------"); + + if !stderr.is_empty() { + logging!(info, Type::Validate, "stderr输出:\n{}", stderr); + } + + if has_error { + logging!(info, Type::Validate, "发现错误,开始处理错误信息"); + let error_msg = if !stdout.is_empty() { + stdout.into() + } else if !stderr.is_empty() { + stderr.into() + } else if let Some(code) = output.status.code() { + format!("验证进程异常退出,退出码: {code}") + } else { + "验证进程被终止".into() + }; + + logging!(info, Type::Validate, "-------- 验证结束 --------"); + Ok((false, error_msg)) // 返回错误消息给调用者处理 + } else { + logging!(info, Type::Validate, "验证成功"); + logging!(info, Type::Validate, "-------- 验证结束 --------"); + Ok((true, String::new())) + } + } + + /// 验证运行时配置 + pub async fn validate_config(&self) -> Result<(bool, String)> { + if *self.process_status.lock().await == ValidationProcessStatus::Ongoing { + logging!(info, Type::Validate, "验证已在进行中,跳过新的验证请求"); + return Ok((true, String::new())); + } + *self.process_status.lock().await = ValidationProcessStatus::Ongoing; + logging!(info, Type::Validate, "生成临时配置文件用于验证"); + + let result = async { + let config_path = Config::generate_file(ConfigType::Check).await?; + let config_path = dirs::path_to_str(&config_path)?; + Self::validate_config_internal(config_path).await + } + .await; + + *self.process_status.lock().await = ValidationProcessStatus::Completed; + result + } +} + +fn has_ext>(path: P, ext: &str) -> bool { + path.as_ref() + .extension() + .and_then(|s| s.to_str()) + .map(|s| s.eq_ignore_ascii_case(ext)) + .unwrap_or(false) +} + +singleton_lazy!( + CoreConfigValidator, + CORECONFIGVALIDATOR, + CoreConfigValidator::new +); diff --git a/src-tauri/src/utils/logging.rs b/src-tauri/src/utils/logging.rs index c40daf23..6718b357 100644 --- a/src-tauri/src/utils/logging.rs +++ b/src-tauri/src/utils/logging.rs @@ -30,6 +30,7 @@ pub enum Type { Network, ProxyMode, // Cache, + Validate, ClashVergeRev, } @@ -53,6 +54,7 @@ impl fmt::Display for Type { Type::Network => write!(f, "[Network]"), Type::ProxyMode => write!(f, "[ProxMode]"), // Type::Cache => write!(f, "[Cache]"), + Type::Validate => write!(f, "[Validate]"), Type::ClashVergeRev => write!(f, "[ClashVergeRev]"), } }