pub use self::pending_answer::PendingAnswer;
pub use self::reply::Reply;
use crate::prompt::{AnswerPromptError, Confirm, Confirmation, Select, Text};
mod pending_answer;
mod reply;
#[derive(Debug)]
pub enum PromptRequest {
Confirm {
question: Confirm,
reply: Reply<Confirmation>,
},
Select {
question: Select,
reply: Reply<usize>,
},
Text {
question: Text,
reply: Reply<String>,
},
}
impl PromptRequest {
pub fn confirm(question: Confirm) -> (Self, PendingAnswer<Confirmation>) {
let (reply, pending) = Reply::channel();
(Self::Confirm { question, reply }, pending)
}
pub fn select(question: Select) -> (Self, PendingAnswer<usize>) {
let (reply, pending) = Reply::channel();
(Self::Select { question, reply }, pending)
}
pub fn text(question: Text) -> (Self, PendingAnswer<String>) {
let (reply, pending) = Reply::channel();
(Self::Text { question, reply }, pending)
}
pub fn question(&self) -> &str {
match self {
Self::Confirm { question, .. } => question.question(),
Self::Select { question, .. } => question.question(),
Self::Text { question, .. } => question.question(),
}
}
pub fn fail(self, error: AnswerPromptError) {
match self {
Self::Confirm { reply, .. } => reply.fail(error),
Self::Select { reply, .. } => reply.fail(error),
Self::Text { reply, .. } => reply.fail(error),
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::missing_panics_doc)]
use super::*;
#[tokio::test]
async fn confirm_with_a_dropped_request_reports_the_drop() {
let (request, pending) = PromptRequest::confirm(Confirm::new("Release?"));
drop(request);
let error = pending.wait().await.expect_err("should fail");
assert!(match error {
AnswerPromptError::DroppedRequest => true,
AnswerPromptError::ClosedInput => false,
AnswerPromptError::UnscriptedPrompt => false,
AnswerPromptError::UnusableTerminal { .. } => false,
});
}
#[tokio::test]
async fn confirm_with_an_answer_resolves_the_pending_answer() {
let (request, pending) = PromptRequest::confirm(Confirm::new("Release?"));
match request {
PromptRequest::Confirm { reply, .. } => reply.answer(Confirmation::Yes),
PromptRequest::Select { .. } | PromptRequest::Text { .. } => {}
}
let answer = pending.wait().await.expect("should answer");
assert_eq!(answer, Confirmation::Yes);
}
#[tokio::test]
async fn fail_tells_the_command_why_the_prompt_has_no_answer() {
let (request, pending) = PromptRequest::text(Text::new("Title"));
request.fail(AnswerPromptError::ClosedInput);
let error = pending.wait().await.expect_err("should fail");
assert_eq!(
error.to_string(),
"the input ended before the user answered"
);
}
#[test]
fn question_with_a_selection_returns_its_text() {
let (request, _pending) = PromptRequest::select(Select::new("Category", ["Added"]));
let question = request.question();
assert_eq!(question, "Category");
}
#[tokio::test]
async fn select_with_an_answer_resolves_the_pending_answer() {
let (request, pending) = PromptRequest::select(Select::new("Category", ["Added", "Fixed"]));
match request {
PromptRequest::Select { reply, .. } => reply.answer(1),
PromptRequest::Confirm { .. } | PromptRequest::Text { .. } => {}
}
let answer = pending.wait().await.expect("should answer");
assert_eq!(answer, 1);
}
#[tokio::test]
async fn text_with_an_answer_resolves_the_pending_answer() {
let (request, pending) = PromptRequest::text(Text::new("Title"));
match request {
PromptRequest::Text { reply, .. } => reply.answer("Fix the race".to_owned()),
PromptRequest::Confirm { .. } | PromptRequest::Select { .. } => {}
}
let answer = pending.wait().await.expect("should answer");
assert_eq!(answer, "Fix the race");
}
#[test]
fn trait_send() {
fn assert_send<T: Send>() {}
assert_send::<PromptRequest>();
}
#[test]
fn trait_sync() {
fn assert_sync<T: Sync>() {}
assert_sync::<PromptRequest>();
}
#[test]
fn trait_unpin() {
fn assert_unpin<T: Unpin>() {}
assert_unpin::<PromptRequest>();
}
}