use anyhow::{Context, Result};
use std::io::IsTerminal;
use crate::oneshot::errors::UsageError;
pub const PROMPT_ADD: &str = "New user password: ";
pub const PROMPT_PASSWORD: &str = "New password: ";
pub fn resolve(provided: Option<String>, prompt: &str) -> Result<String> {
if let Some(p) = provided {
if p.is_empty() {
return Err(UsageError::new("password must not be empty").into());
}
return Ok(p);
}
let raw = if std::io::stdin().is_terminal() {
rpassword::prompt_password(prompt).context("read password from terminal")?
} else {
let mut line = String::new();
std::io::stdin()
.read_line(&mut line)
.context("read password from stdin")?;
line
};
finalize_read_password(&raw)
}
pub(crate) fn finalize_read_password(raw: &str) -> Result<String> {
let trimmed = raw.trim_end_matches(['\r', '\n']).to_string();
if trimmed.is_empty() {
return Err(UsageError::new("password must not be empty").into());
}
Ok(trimmed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn provided_some_passes_through() {
assert_eq!(
resolve(Some("hunter2".into()), "ignored").unwrap(),
"hunter2"
);
}
#[test]
fn provided_some_empty_rejected_as_usage() {
let err = resolve(Some(String::new()), "ignored").unwrap_err();
let chain: Vec<_> = err.chain().collect();
assert!(
chain.iter().any(|c| c.is::<UsageError>()),
"empty positional should classify as usage error"
);
}
#[test]
fn finalize_strips_trailing_crlf_and_returns_value() {
assert_eq!(finalize_read_password("hunter2\n").unwrap(), "hunter2");
assert_eq!(finalize_read_password("hunter2\r\n").unwrap(), "hunter2");
assert_eq!(finalize_read_password("hunter2").unwrap(), "hunter2");
}
#[test]
fn finalize_keeps_internal_whitespace() {
assert_eq!(finalize_read_password("hun ter2\n").unwrap(), "hun ter2");
}
#[test]
fn finalize_empty_after_trim_classifies_as_usage_error() {
for input in ["", "\n", "\r\n", "\r"] {
let err = finalize_read_password(input).unwrap_err();
let chain: Vec<_> = err.chain().collect();
assert!(
chain.iter().any(|c| c.is::<UsageError>()),
"empty stdin input ({input:?}) should classify as usage error"
);
}
}
}