use directories::ProjectDirs;
use std::fs;
use std::path::PathBuf;
use super::File;
#[derive(Debug, Clone)]
pub struct ValidationResult {
pub valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl ValidationResult {
#[must_use]
pub fn ok() -> Self {
Self {
valid: true,
errors: Vec::new(),
warnings: Vec::new(),
}
}
#[must_use]
pub fn with_warning(mut self, warning: impl Into<String>) -> Self {
self.warnings.push(warning.into());
self
}
#[must_use]
pub fn error(msg: impl Into<String>) -> Self {
Self {
valid: false,
errors: vec![msg.into()],
warnings: Vec::new(),
}
}
#[must_use]
pub fn with_error(mut self, error: impl Into<String>) -> Self {
self.errors.push(error.into());
self.valid = false;
self
}
#[must_use]
pub fn merge(mut self, other: ValidationResult) -> Self {
if !other.valid {
self.valid = false;
}
self.errors.extend(other.errors);
self.warnings.extend(other.warnings);
self
}
}
fn validate_csv_delimiter_config(delimiter: char) -> Result<(), String> {
if !",;|\t".contains(delimiter) {
return Err(format!(
"Invalid CSV delimiter '{}'. Must be one of: comma, semicolon, pipe, or tab",
delimiter
));
}
Ok(())
}
pub fn validate_config(file_config: &File) -> ValidationResult {
let mut result = ValidationResult::ok();
if let Some(ref profile) = file_config.profile {
if let Err(e) = crate::profiles::UserProfile::validate(profile) {
result = result.with_error(e);
}
}
if let Some(ref theme) = file_config.theme {
if let Err(e) = crate::theme::Theme::validate(theme) {
result = result.with_error(e);
}
}
if let Some(delimiter) = file_config.csv_delimiter {
if let Err(e) = validate_csv_delimiter_config(delimiter) {
result = result.with_error(e);
}
}
if file_config.simple.unwrap_or(false) {
result = result.with_warning(
"'simple' option is deprecated. Use '--format simple' instead.".to_string(),
);
}
if file_config.csv.unwrap_or(false) {
result = result
.with_warning("'csv' option is deprecated. Use '--format csv' instead.".to_string());
}
if file_config.json.unwrap_or(false) {
result = result
.with_warning("'json' option is deprecated. Use '--format json' instead.".to_string());
}
result
}
#[must_use]
pub fn get_config_path_internal() -> Option<PathBuf> {
ProjectDirs::from("dev", "vibe", "netspeed-cli").map(|proj_dirs| {
let config_dir = proj_dirs.config_dir();
if let Err(e) = fs::create_dir_all(config_dir) {
eprintln!("Warning: Failed to create config directory: {e}");
}
config_dir.join("config.toml")
})
}
pub fn load_config_file() -> Option<File> {
let path = get_config_path_internal()?;
if !path.exists() {
return None;
}
let content = match fs::read_to_string(&path) {
Ok(c) => c,
Err(e) => {
eprintln!(
"Warning: Failed to read config file {}: {e}",
path.display()
);
return None;
}
};
let mut config: File = match toml::from_str(&content) {
Ok(c) => c,
Err(e) => {
eprintln!("Warning: Failed to parse config: {e}");
return None;
}
};
if let Some(timeout) = config.timeout {
if timeout == 0 || timeout > 300 {
eprintln!(
"Warning: Invalid config timeout ({timeout}s, must be 1-300). Using default."
);
config.timeout = None;
}
}
Some(config)
}