use clap::Subcommand;
use figment::{
providers::{Env, Format, Serialized, Toml},
Figment,
};
use serde::{Deserialize, Serialize};
use crate::project::CheckmateProject;
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Config {
#[serde(default)]
pub env: EnvConfig,
#[serde(default)]
pub defaults: DefaultsConfig,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct EnvConfig {
pub base_url: Option<String>,
#[serde(default = "default_timeout")]
pub timeout_ms: u64,
}
impl Default for EnvConfig {
fn default() -> Self {
Self {
base_url: None,
timeout_ms: default_timeout(),
}
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct DefaultsConfig {
#[serde(default)]
pub fail_fast: bool,
#[serde(default = "default_status")]
pub expect_status: u16,
}
fn default_timeout() -> u64 {
5000
}
fn default_status() -> u16 {
200
}
impl Default for Config {
fn default() -> Self {
Self {
env: EnvConfig::default(),
defaults: DefaultsConfig::default(),
}
}
}
impl Default for DefaultsConfig {
fn default() -> Self {
Self {
fail_fast: false,
expect_status: default_status(),
}
}
}
impl Config {
pub fn load(project: Option<&CheckmateProject>) -> Self {
let mut figment = Figment::new().merge(Serialized::defaults(Config::default()));
if let Some(config_dir) = directories::ProjectDirs::from("", "", "checkmate") {
let user_config = config_dir.config_dir().join("config.toml");
if user_config.exists() {
figment = figment.merge(Toml::file(user_config));
}
}
if let Some(project) = project {
if project.config_path.exists() {
figment = figment.merge(Toml::file(&project.config_path));
}
}
figment = figment.merge(Env::prefixed("CM_").split("__"));
figment.extract().unwrap_or_default()
}
pub fn load_auto() -> Self {
let project = CheckmateProject::discover();
Self::load(project.as_ref())
}
}
#[derive(Subcommand)]
pub enum ConfigCommands {
Show {
#[arg(long)]
json: bool,
},
Sources,
}
pub fn run(command: ConfigCommands) -> Result<(), Box<dyn std::error::Error>> {
match command {
ConfigCommands::Show { json } => show_config(json),
ConfigCommands::Sources => show_sources(),
}
}
fn show_config(as_json: bool) -> Result<(), Box<dyn std::error::Error>> {
let config = Config::load_auto();
if as_json {
println!("{}", serde_json::to_string_pretty(&config)?);
} else {
println!("{}", toml::to_string_pretty(&config)?);
}
Ok(())
}
fn show_sources() -> Result<(), Box<dyn std::error::Error>> {
println!("Configuration sources (in priority order):\n");
if let Some(config_dir) = directories::ProjectDirs::from("", "", "checkmate") {
let user_config = config_dir.config_dir().join("config.toml");
if user_config.exists() {
println!(" [✓] User: {}", user_config.display());
} else {
println!(" [ ] User: {} (not found)", user_config.display());
}
}
if let Some(project) = CheckmateProject::discover() {
if project.config_path.exists() {
println!(" [✓] Project: {}", project.config_path.display());
} else {
println!(" [ ] Project: {} (not found)", project.config_path.display());
}
} else {
println!(" [ ] Project: (no .checkmate/ found)");
}
println!("\n Environment variables: CM_* (e.g., CM_ENV__BASE_URL, CM_DEFAULTS__FAIL_FAST)");
Ok(())
}