use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct File {
#[serde(default)]
pub output: Option<FileOutput>,
#[serde(default)]
pub test: Option<FileTest>,
#[serde(default)]
pub network: Option<FileNetwork>,
#[serde(default)]
pub server: Option<FileServer>,
#[serde(default)]
pub profile: Option<String>,
#[serde(default)]
pub theme: Option<String>,
#[serde(default)]
pub bytes: Option<bool>,
#[serde(default)]
pub simple: Option<bool>,
#[serde(default)]
pub csv: Option<bool>,
#[serde(default)]
pub csv_delimiter: Option<char>,
#[serde(default)]
pub csv_header: Option<bool>,
#[serde(default)]
pub json: Option<bool>,
#[serde(default)]
pub no_download: Option<bool>,
#[serde(default)]
pub no_upload: Option<bool>,
#[serde(default)]
pub single: Option<bool>,
#[serde(default)]
pub timeout: Option<u64>,
#[serde(default)]
pub bind: Option<String>,
#[serde(default)]
pub ca_cert: Option<String>,
#[serde(default)]
pub tls_version: Option<String>,
#[serde(default)]
pub pin_certs: Option<bool>,
#[serde(default)]
pub insecure: Option<bool>,
#[serde(default)]
pub server_ids: Option<Vec<u64>>,
#[serde(default)]
pub exclude_ids: Option<Vec<u64>>,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct FileOutput {
pub bytes: Option<bool>,
pub simple: Option<bool>,
pub csv: Option<bool>,
pub csv_delimiter: Option<char>,
pub csv_header: Option<bool>,
pub json: Option<bool>,
pub minimal: Option<bool>,
pub profile: Option<String>,
pub theme: Option<String>,
pub format: Option<super::Format>,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct FileTest {
pub no_download: Option<bool>,
pub no_upload: Option<bool>,
pub single: Option<bool>,
pub timeout: Option<u64>,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct FileNetwork {
pub bind: Option<String>,
pub timeout: Option<u64>,
pub ca_cert: Option<String>,
pub tls_version: Option<String>,
pub pin_certs: Option<bool>,
pub insecure: Option<bool>,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct FileServer {
pub server_ids: Option<Vec<u64>>,
pub exclude_ids: Option<Vec<u64>>,
}
#[derive(Debug, Clone)]
pub struct ValidationResult {
pub valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
impl ValidationResult {
pub fn ok() -> Self {
Self {
valid: true,
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn valid() -> Self {
Self {
valid: true,
errors: Vec::new(),
warnings: Vec::new(),
}
}
pub fn with_error(mut self, error: impl Into<String>) -> Self {
self.valid = false;
self.errors.push(error.into());
self
}
pub fn with_warning(mut self, warning: impl Into<String>) -> Self {
self.warnings.push(warning.into());
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 let Some(timeout) = file_config.timeout {
if timeout == 0 {
result = result.with_error("Timeout cannot be zero");
}
}
let pin_certs = file_config.pin_certs.unwrap_or(false);
let insecure = file_config.insecure.unwrap_or(false);
if insecure && pin_certs {
result = result.with_error("Cannot use both 'insecure' and 'pin_certs'");
}
result
}
pub fn get_config_path_internal() -> Option<PathBuf> {
directories::ProjectDirs::from("com", "netspeed-cli", "netspeed-cli")
.map(|dirs| dirs.config_dir().join("config.yaml"))
}
pub fn load_config_file() -> Option<File> {
get_config_path_internal().and_then(|path| {
if path.exists() {
serde_yaml::from_reader(std::fs::File::ok()?).ok()
} else {
None
}
})
}