use std::io::{self, Write};
use std::path::Path;
use serde_json::json;
use crate::cli::ConfigPathArgs;
use crate::common::paths;
pub fn run_path(explicit: Option<&Path>, args: &ConfigPathArgs) -> i32 {
let mut stdout = io::stdout().lock();
let mut stderr = io::stderr().lock();
let result = if args.json {
write_json(&mut stdout, explicit)
} else {
write_text(&mut stdout, explicit)
};
if let Err(e) = result {
let _ = writeln!(stderr, "error: failed to write config path: {e}");
return 1;
}
0
}
fn write_text<W: Write>(out: &mut W, explicit: Option<&Path>) -> io::Result<()> {
if let Some(p) = explicit {
writeln!(out, "{}", paths::format_path_with_existence(Some(p)))?;
writeln!(out, "source: --config override (discovery bypassed)")?;
return Ok(());
}
let active = paths::active_config_path();
writeln!(
out,
"{}",
paths::format_path_with_existence(active.as_deref())
)?;
let candidates = paths::candidate_config_paths();
if candidates.is_empty() {
return Ok(());
}
writeln!(out, "search order:")?;
for (i, candidate) in candidates.iter().enumerate() {
writeln!(out, " {}. {}", i + 1, candidate.display())?;
}
Ok(())
}
fn write_json<W: Write>(out: &mut W, explicit: Option<&Path>) -> io::Result<()> {
let overridden = explicit.is_some();
let active_path: Option<std::path::PathBuf> = match explicit {
Some(p) => Some(p.to_path_buf()),
None => paths::active_config_path(),
};
let exists = active_path.as_deref().map(Path::exists).unwrap_or(false);
let search_order: Vec<String> = if overridden {
Vec::new()
} else {
paths::candidate_config_paths()
.into_iter()
.map(|p| p.display().to_string())
.collect()
};
let value = json!({
"active": active_path.as_deref().map(|p| p.display().to_string()),
"exists": exists,
"overridden": overridden,
"search_order": search_order,
});
let rendered = serde_json::to_string_pretty(&value).map_err(io::Error::other)?;
writeln!(out, "{rendered}")?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_output_never_empty_with_default_resolution() {
let mut buf: Vec<u8> = Vec::new();
write_text(&mut buf, None).expect("write must succeed");
let s = String::from_utf8(buf).unwrap();
assert!(!s.trim().is_empty(), "text output must not be empty");
}
#[test]
fn text_output_with_explicit_reports_override() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("config.toml");
std::fs::write(&path, b"# stub").unwrap();
let mut buf: Vec<u8> = Vec::new();
write_text(&mut buf, Some(&path)).expect("write must succeed");
let s = String::from_utf8(buf).unwrap();
assert!(s.contains(&path.display().to_string()));
assert!(
s.contains("(active)"),
"existing override file must be marked (active), got: {s}"
);
assert!(
s.contains("--config override"),
"override sourcing must be disclosed, got: {s}"
);
assert!(
!s.contains("search order:"),
"search order must be suppressed when overridden, got: {s}"
);
}
#[test]
fn text_output_prints_search_order_when_not_overridden() {
let mut buf: Vec<u8> = Vec::new();
write_text(&mut buf, None).expect("write must succeed");
let s = String::from_utf8(buf).unwrap();
if paths::candidate_config_paths().is_empty() {
assert!(s.contains("no config path"));
} else {
assert!(s.contains("search order:"));
}
}
#[test]
fn json_output_has_stable_schema() {
let mut buf: Vec<u8> = Vec::new();
write_json(&mut buf, None).expect("write must succeed");
let s = String::from_utf8(buf).unwrap();
let value: serde_json::Value = serde_json::from_str(&s).expect("valid JSON");
assert!(value.get("active").is_some(), "missing `active`");
assert!(value.get("exists").is_some(), "missing `exists`");
assert!(value.get("overridden").is_some(), "missing `overridden`");
assert!(
value.get("search_order").is_some(),
"missing `search_order`"
);
assert_eq!(value["overridden"], false);
}
#[test]
fn json_output_reflects_override() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("config.toml");
std::fs::write(&path, b"# stub").unwrap();
let mut buf: Vec<u8> = Vec::new();
write_json(&mut buf, Some(&path)).expect("write must succeed");
let s = String::from_utf8(buf).unwrap();
let value: serde_json::Value = serde_json::from_str(&s).expect("valid JSON");
assert_eq!(value["overridden"], true);
assert_eq!(value["exists"], true);
assert_eq!(value["active"], path.display().to_string());
let order = value["search_order"]
.as_array()
.expect("search_order must be array");
assert!(
order.is_empty(),
"search_order must be empty when overridden, got: {order:?}"
);
}
#[test]
fn run_path_does_not_create_file() {
let dir = tempfile::tempdir().unwrap();
let absent = dir.path().join("never-written.toml");
assert!(!absent.exists(), "precondition: file must not exist");
let mut buf: Vec<u8> = Vec::new();
write_json(&mut buf, Some(&absent)).expect("write must succeed");
assert!(
!absent.exists(),
"config path runner must NOT create the file"
);
}
}