use anyhow::{Context, Result};
use clap::ArgMatches;
use clap::parser::ValueSource;
use sbom_tools::config::{
AppConfig, Validatable, discover_config_file, load_config_file, load_or_default,
};
use std::path::Path;
pub struct EffectiveConfig {
config: AppConfig,
loaded_from: Option<std::path::PathBuf>,
}
impl EffectiveConfig {
pub fn load(explicit_path: Option<&Path>, no_config: bool) -> Result<Self> {
if no_config {
return Ok(Self {
config: AppConfig::default(),
loaded_from: None,
});
}
if let Some(path) = explicit_path
&& !path.exists()
{
anyhow::bail!("config file not found: {}", path.display());
}
let Some(path) = discover_config_file(explicit_path) else {
return Ok(Self {
config: AppConfig::default(),
loaded_from: None,
});
};
let config = load_config_file(&path)
.with_context(|| format!("failed to load config file {}", path.display()))?;
Self::validate(&config, Some(&path))?;
Ok(Self {
config,
loaded_from: Some(path),
})
}
#[must_use]
pub fn load_lenient(explicit_path: Option<&Path>, no_config: bool) -> Self {
if no_config {
return Self {
config: AppConfig::default(),
loaded_from: None,
};
}
let (config, loaded_from) = load_or_default(explicit_path);
Self {
config,
loaded_from,
}
}
fn validate(config: &AppConfig, source: Option<&Path>) -> Result<()> {
let errors = config.validate();
if errors.is_empty() {
return Ok(());
}
let where_ = source.map_or_else(
|| "configuration".to_string(),
|p| format!("config file {}", p.display()),
);
let detail = errors
.iter()
.map(|e| format!(" - {e}"))
.collect::<Vec<_>>()
.join("\n");
anyhow::bail!("invalid {where_}:\n{detail}");
}
pub fn loaded_from(&self) -> Option<&Path> {
self.loaded_from.as_deref()
}
pub fn into_app_config(self) -> AppConfig {
self.config
}
}
#[must_use]
pub fn arg_was_set(matches: &ArgMatches, name: &str) -> bool {
matches!(matches.value_source(name), Some(ValueSource::CommandLine))
}
#[must_use]
pub fn arg_was_set_sub(matches: Option<&ArgMatches>, name: &str) -> bool {
matches.is_some_and(|m| arg_was_set(m, name))
}
#[must_use]
pub fn resolve<T>(cli_value: T, was_set: bool, file_value: Option<T>) -> T {
if was_set {
cli_value
} else {
file_value.unwrap_or(cli_value)
}
}
#[must_use]
pub const fn resolve_bool(cli_value: bool, file_value: bool) -> bool {
cli_value || file_value
}