use crate::error::Result;
pub trait AskHandler: Send + Sync {
fn ask_user(&self, question: &str) -> Result<String>;
}
pub struct NoninteractiveAskHandler;
impl AskHandler for NoninteractiveAskHandler {
fn ask_user(&self, _question: &str) -> Result<String> {
Err(crate::error::CruiseError::Other(
"ask_user is unavailable in this non-interactive context".to_string(),
))
}
}
pub struct CliAskHandler;
impl AskHandler for CliAskHandler {
fn ask_user(&self, question: &str) -> Result<String> {
crate::multiline_input::prompt_multiline(question)?.into_result()
}
}
#[cfg(test)]
pub struct ScriptedAskHandler {
answers: std::sync::Mutex<std::collections::VecDeque<String>>,
pub asked: std::sync::Mutex<Vec<String>>,
}
#[cfg(test)]
impl ScriptedAskHandler {
#[must_use]
pub fn new(answers: impl IntoIterator<Item = String>) -> Self {
Self {
answers: std::sync::Mutex::new(answers.into_iter().collect()),
asked: std::sync::Mutex::new(Vec::new()),
}
}
}
#[cfg(test)]
impl AskHandler for ScriptedAskHandler {
fn ask_user(&self, question: &str) -> Result<String> {
if let Ok(mut asked) = self.asked.lock() {
asked.push(question.to_string());
}
let next = self.answers.lock().ok().and_then(|mut q| q.pop_front());
match next {
Some(answer) => Ok(answer),
None => Err(crate::error::CruiseError::Other(
"ScriptedAskHandler: no more scripted answers".to_string(),
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scripted_handler_returns_answers_in_order() {
let h = ScriptedAskHandler::new(["first".to_string(), "second".to_string()]);
assert_eq!(
h.ask_user("q1").unwrap_or_else(|e| panic!("{e:?}")),
"first"
);
assert_eq!(
h.ask_user("q2").unwrap_or_else(|e| panic!("{e:?}")),
"second"
);
}
#[test]
fn scripted_handler_records_questions() {
let h = ScriptedAskHandler::new(["a".to_string()]);
let _ = h.ask_user("why?");
let asked = h.asked.lock().unwrap_or_else(|e| panic!("{e:?}"));
assert_eq!(asked.as_slice(), &["why?".to_string()]);
}
#[test]
fn scripted_handler_errors_when_exhausted() {
let h = ScriptedAskHandler::new(std::iter::empty());
assert!(h.ask_user("q").is_err(), "exhausted handler should error");
}
#[test]
fn noninteractive_handler_errors_instead_of_blocking() {
let h = NoninteractiveAskHandler;
let err = match h.ask_user("anything") {
Err(e) => e.to_string(),
Ok(_) => panic!("expected error from non-interactive handler"),
};
assert!(
err.contains("non-interactive"),
"error should explain why: {err}"
);
}
}