use crate::cli::Cli;
use crate::config::{
Config, ConfigLoadError, ConfigLoadProvenance, ConfigStatus, config_file_path, load_config,
load_config_from_path, load_config_from_path_with_provenance, mask_home_path,
};
fn report_history_migration_warning(warning: &str) {
eprintln!("Warning: {warning}");
log::warn!("{warning}");
}
#[derive(Debug)]
pub(crate) struct ConfigLoadWarning {
pub(crate) code: &'static str,
pub(crate) message: String,
pub(crate) path: std::path::PathBuf,
}
pub(crate) fn load_config_with_fallback(
cli: &Cli,
) -> (Config, Option<std::path::PathBuf>, ConfigStatus) {
let (result, config_path) = if let Some(path) = &cli.r_source.config {
(load_config_from_path(path), Some(path.clone()))
} else {
let default_path = config_file_path();
(load_config(), default_path)
};
match result {
Ok(mut config) => {
if let Some(warning) = config.history_migration_warning.take() {
report_history_migration_warning(&warning);
}
(config, config_path, ConfigStatus::Ok)
}
Err(e) => {
let (raw_path, masked_path, source_msg, status) = match &e {
ConfigLoadError::Read { path, source } => (
path.display().to_string(),
mask_home_path(path),
source.to_string(),
ConfigStatus::ReadError,
),
ConfigLoadError::Parse { path, source } => (
path.display().to_string(),
mask_home_path(path),
source.to_string(),
ConfigStatus::ParseError,
),
ConfigLoadError::Validation { path, message } => (
path.display().to_string(),
mask_home_path(path),
message.clone(),
ConfigStatus::ParseError,
),
};
eprintln!(
"Warning: Failed to load config from {}: {}",
masked_path, source_msg
);
eprintln!(
" Using default configuration. Run `arf config check` to see details."
);
log::warn!("Config load error for {}: {}", raw_path, source_msg);
(Config::default(), config_path, status)
}
}
}
pub(crate) fn load_config_or_warn(config_path: Option<&std::path::PathBuf>) -> Config {
let result = if let Some(path) = config_path {
load_config_from_path(path)
} else {
load_config()
};
match result {
Ok(mut config) => {
if let Some(warning) = config.history_migration_warning.take() {
report_history_migration_warning(&warning);
}
config
}
Err(e) => {
let (path_display, source_msg) = match &e {
ConfigLoadError::Read { path, source } => {
(mask_home_path(path), source.to_string())
}
ConfigLoadError::Parse { path, source } => {
(mask_home_path(path), source.to_string())
}
ConfigLoadError::Validation { path, message } => {
(mask_home_path(path), message.clone())
}
};
eprintln!(
"Warning: Failed to load config from {}: {}",
path_display, source_msg
);
eprintln!(" Using default configuration.");
Config::default()
}
}
}
pub(crate) fn load_config_collecting_warnings(
config_path: Option<&std::path::PathBuf>,
warnings: &mut Vec<String>,
) -> Config {
let result = if let Some(path) = config_path {
load_config_from_path(path)
} else {
load_config()
};
match result {
Ok(mut config) => {
if let Some(warning) = config.history_migration_warning.take() {
log::warn!("{warning}");
warnings.push(warning);
}
config
}
Err(e) => {
let (path_display, source_msg) = match &e {
ConfigLoadError::Read { path, source } => {
(mask_home_path(path), source.to_string())
}
ConfigLoadError::Parse { path, source } => {
(mask_home_path(path), source.to_string())
}
ConfigLoadError::Validation { path, message } => {
(mask_home_path(path), message.clone())
}
};
warnings.push(format!(
"Failed to load config from {path_display}: {source_msg}. Using default configuration."
));
Config::default()
}
}
}
pub(crate) fn load_config_collecting_diagnostics(
config_path: Option<&std::path::PathBuf>,
warnings: &mut Vec<ConfigLoadWarning>,
) -> (Config, Option<ConfigLoadProvenance>) {
let path = config_path.cloned().or_else(config_file_path);
let result = if let Some(path) = path.as_deref() {
load_config_from_path_with_provenance(path)
} else {
Ok((Config::default(), None))
};
match result {
Ok((mut config, provenance)) => {
let warning = config.history_migration_warning.take();
if let (Some(warning), Some(provenance)) = (&warning, provenance.as_ref()) {
log::warn!("{warning}");
warnings.push(ConfigLoadWarning {
code: "config.history_disabled_deprecated",
message: warning.clone(),
path: provenance.path.clone(),
});
}
let provenance = provenance.map(|mut provenance| {
provenance.history_migration_warning = warning;
provenance
});
(config, provenance)
}
Err(error) => {
let (code, path, message) = match error {
ConfigLoadError::Read { path, source } => {
("config.read_failed", path, source.to_string())
}
ConfigLoadError::Parse { path, source } => {
("config.parse_failed", path, source.to_string())
}
ConfigLoadError::Validation { path, message } => {
("config.parse_failed", path, message)
}
};
warnings.push(ConfigLoadWarning {
code,
message: format!(
"Failed to load config from {}: {}. Using default configuration.",
path.display(),
message
),
path,
});
(Config::default(), None)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deprecated_history_disabled_is_collected_for_headless_output() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("arf.toml");
std::fs::write(&path, "[history]\ndisabled = true\n").unwrap();
let mut warnings = Vec::new();
let config = load_config_collecting_warnings(Some(&path), &mut warnings);
assert!(matches!(
config.history.mode,
crate::config::HistoryMode::Volatile
));
assert_eq!(warnings.len(), 1);
assert!(warnings[0].contains("history.mode = \"volatile\""));
}
#[test]
fn deprecated_history_disabled_has_a_stable_diagnostic_code() {
let temp = tempfile::tempdir().unwrap();
let path = temp.path().join("arf.toml");
std::fs::write(&path, "[history]\ndisabled = false\n").unwrap();
let mut warnings = Vec::new();
let (_, provenance) = load_config_collecting_diagnostics(Some(&path), &mut warnings);
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].code, "config.history_disabled_deprecated");
assert!(
provenance
.unwrap()
.history_migration_warning
.unwrap()
.contains("persistent")
);
}
}