use std::io::IsTerminal;
use crate::domain::{error::DotLockError, model::DotLockResult};
#[derive(Debug, PartialEq, Eq)]
enum Gate {
Skip,
Fail,
Prompt,
}
fn gate(yes: bool, stdin_is_tty: bool) -> Gate {
if yes {
Gate::Skip
} else if stdin_is_tty {
Gate::Prompt
} else {
Gate::Fail
}
}
pub fn confirm_destructive(action: &str, yes: bool) -> DotLockResult<()> {
match gate(yes, std::io::stdin().is_terminal()) {
Gate::Skip => Ok(()),
Gate::Fail => Err(DotLockError::ConfirmationRequired {
action: action.to_string(),
}),
Gate::Prompt => {
let confirmed = inquire::Confirm::new(&format!("{action}?"))
.with_default(false)
.prompt()
.map_err(|err| match err {
inquire::InquireError::OperationCanceled
| inquire::InquireError::OperationInterrupted => DotLockError::Aborted,
other => DotLockError::Io(other.to_string()),
})?;
if confirmed {
Ok(())
} else {
Err(DotLockError::Aborted)
}
}
}
}
#[cfg(test)]
mod tests {
use super::{Gate, gate};
use crate::domain::error::DotLockError;
#[test]
fn yes_always_skips_and_no_tty_always_fails() {
assert_eq!(gate(true, true), Gate::Skip);
assert_eq!(gate(true, false), Gate::Skip);
assert_eq!(gate(false, true), Gate::Prompt);
assert_eq!(gate(false, false), Gate::Fail);
}
#[test]
fn confirmation_required_error_hints_at_yes_flag() {
let err = DotLockError::ConfirmationRequired {
action: "remove secret FOO".to_string(),
};
assert!(err.to_string().contains("remove secret FOO"));
assert!(err.hint().expect("hint").contains("--yes"));
}
}