use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunConfig {
#[serde(default)]
pub base_url: Option<String>,
#[serde(default = "default_output")]
pub output: PathBuf,
#[serde(default)]
pub headers: HashMap<String, String>,
#[serde(default = "default_timeout")]
pub timeout_secs: u64,
}
fn default_output() -> PathBuf {
PathBuf::from("./output")
}
fn default_timeout() -> u64 {
30
}
impl Default for RunConfig {
fn default() -> Self {
Self {
base_url: None,
output: default_output(),
headers: HashMap::new(),
timeout_secs: default_timeout(),
}
}
}
impl RunConfig {
pub fn load(path: &str) -> anyhow::Result<Self> {
let content = std::fs::read_to_string(path)?;
let config: RunConfig = toml::from_str(&content)?;
Ok(config)
}
pub fn load_optional(path: &str) -> Self {
std::fs::read_to_string(path)
.ok()
.and_then(|content| toml::from_str(&content).ok())
.unwrap_or_default()
}
pub fn merge(&mut self, base_url: Option<String>, output: Option<PathBuf>) {
if let Some(url) = base_url {
self.base_url = Some(url);
}
if let Some(out) = output {
self.output = out;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_config_toml() {
let toml = r#"
base_url = "http://localhost:8080"
output = "./test-output"
timeout_secs = 60
[headers]
Authorization = "Bearer test-token"
"#;
let config: RunConfig = toml::from_str(toml).unwrap();
assert_eq!(config.base_url, Some("http://localhost:8080".to_string()));
assert_eq!(config.output, PathBuf::from("./test-output"));
assert_eq!(config.timeout_secs, 60);
assert_eq!(
config.headers.get("Authorization").unwrap(),
"Bearer test-token"
);
}
#[test]
fn parse_config_defaults() {
let toml = r#"
base_url = "http://localhost:8080"
"#;
let config: RunConfig = toml::from_str(toml).unwrap();
assert_eq!(config.base_url, Some("http://localhost:8080".to_string()));
assert_eq!(config.output, PathBuf::from("./output"));
assert_eq!(config.timeout_secs, 30);
assert!(config.headers.is_empty());
}
#[test]
fn merge_overrides() {
let mut config = RunConfig {
base_url: Some("http://original".to_string()),
output: PathBuf::from("./original"),
headers: HashMap::new(),
timeout_secs: 30,
};
config.merge(
Some("http://override".to_string()),
Some(PathBuf::from("./override")),
);
assert_eq!(config.base_url, Some("http://override".to_string()));
assert_eq!(config.output, PathBuf::from("./override"));
}
#[test]
fn merge_partial() {
let mut config = RunConfig {
base_url: Some("http://original".to_string()),
output: PathBuf::from("./original"),
headers: HashMap::new(),
timeout_secs: 30,
};
config.merge(None, Some(PathBuf::from("./override")));
assert_eq!(config.base_url, Some("http://original".to_string()));
assert_eq!(config.output, PathBuf::from("./override"));
}
}