use crate::error::{CliError, CliResult};
use std::path::{Path, PathBuf};
pub fn resolve_env_file(
explicit: Option<&Path>,
no_env_file: bool,
dir: &Path,
) -> CliResult<Option<PathBuf>> {
if no_env_file {
return Ok(None);
}
if let Some(p) = explicit {
if !p.exists() {
return Err(CliError::EnvFileNotFound {
path: p.to_path_buf(),
});
}
return Ok(Some(p.to_path_buf()));
}
let candidate = dir.join(".env");
Ok(candidate.exists().then_some(candidate))
}
pub fn load_env_file_if_present(path: Option<&Path>) -> CliResult<()> {
if let Some(p) = path {
dotenvy::from_path(p).map_err(|source| CliError::ReadConfig {
path: p.to_path_buf(),
source: std::io::Error::other(source),
})?;
}
Ok(())
}
pub fn discover_config_path(dir: &Path) -> Option<PathBuf> {
const CANDIDATES: &[&str] = &["faucet.yaml", "faucet.yml", "faucet.json"];
let mut chosen: Option<PathBuf> = None;
let mut also: Vec<PathBuf> = Vec::new();
for name in CANDIDATES {
let p = dir.join(name);
if p.exists() {
if chosen.is_none() {
chosen = Some(p);
} else {
also.push(p);
}
}
}
if !also.is_empty()
&& let Some(ref c) = chosen
{
let extras: Vec<String> = also.iter().map(|p| p.display().to_string()).collect();
tracing::warn!(
chosen = %c.display(),
also_present = ?extras,
"multiple faucet.* config files in cwd; picking the first in priority order (yaml > yml > json)"
);
}
chosen
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn resolve_env_file_explicit_wins() {
let dir = tempdir().unwrap();
let p = dir.path().join("secrets.env");
std::fs::write(&p, "X=1\n").unwrap();
let resolved = resolve_env_file(Some(&p), false, dir.path()).unwrap();
assert_eq!(resolved, Some(p));
}
#[test]
fn resolve_env_file_explicit_missing_errors() {
let dir = tempdir().unwrap();
let missing = dir.path().join("nope.env");
let err = resolve_env_file(Some(&missing), false, dir.path()).unwrap_err();
assert!(matches!(err, CliError::EnvFileNotFound { .. }));
}
#[test]
fn resolve_env_file_auto_finds_dotenv_in_cwd() {
let dir = tempdir().unwrap();
let dotenv = dir.path().join(".env");
std::fs::write(&dotenv, "X=1\n").unwrap();
assert_eq!(
resolve_env_file(None, false, dir.path()).unwrap(),
Some(dotenv)
);
}
#[test]
fn resolve_env_file_none_when_missing_and_not_explicit() {
let dir = tempdir().unwrap();
assert_eq!(resolve_env_file(None, false, dir.path()).unwrap(), None);
}
#[test]
fn resolve_env_file_no_env_file_skips_auto_discovery() {
let dir = tempdir().unwrap();
std::fs::write(dir.path().join(".env"), "X=1\n").unwrap();
assert_eq!(resolve_env_file(None, true, dir.path()).unwrap(), None);
}
#[test]
fn resolve_env_file_no_env_file_also_skips_explicit_path() {
let dir = tempdir().unwrap();
let p = dir.path().join("explicit.env");
std::fs::write(&p, "X=1\n").unwrap();
assert_eq!(resolve_env_file(Some(&p), true, dir.path()).unwrap(), None);
}
#[test]
fn discover_config_path_prefers_yaml() {
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("faucet.yaml"), "version: 1\n").unwrap();
std::fs::write(dir.path().join("faucet.yml"), "version: 1\n").unwrap();
std::fs::write(dir.path().join("faucet.json"), "{}").unwrap();
let found = discover_config_path(dir.path());
assert_eq!(found, Some(dir.path().join("faucet.yaml")));
}
#[test]
fn discover_config_path_falls_through_to_yml() {
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("faucet.yml"), "v: 1\n").unwrap();
std::fs::write(dir.path().join("faucet.json"), "{}").unwrap();
assert_eq!(
discover_config_path(dir.path()),
Some(dir.path().join("faucet.yml"))
);
}
#[test]
fn discover_config_path_falls_through_to_json() {
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("faucet.json"), "{}").unwrap();
assert_eq!(
discover_config_path(dir.path()),
Some(dir.path().join("faucet.json"))
);
}
#[test]
fn discover_config_path_none_when_missing() {
let dir = tempdir().unwrap();
assert_eq!(discover_config_path(dir.path()), None);
}
#[test]
fn load_env_file_if_present_is_noop_for_none() {
load_env_file_if_present(None).unwrap();
}
#[test]
fn load_env_file_if_present_propagates_parse_failure_as_readconfig() {
let dir = tempdir().unwrap();
let p = dir.path().join("bad.env");
std::fs::write(&p, "no equals here\n").unwrap();
let err = load_env_file_if_present(Some(&p)).unwrap_err();
assert!(matches!(err, CliError::ReadConfig { .. }));
}
}