use async_trait::async_trait;
pub struct Question {
pub text: String,
pub choices: Vec<String>,
pub default: Option<usize>,
}
pub struct Answer {
pub text: String,
pub index: Option<usize>,
}
#[async_trait]
pub trait Interviewer: Send + Sync {
async fn ask(&self, question: Question) -> Answer;
}
pub struct ConsoleInterviewer;
#[async_trait]
impl Interviewer for ConsoleInterviewer {
async fn ask(&self, question: Question) -> Answer {
use std::io::{self, Write};
println!("\n{}", question.text);
for (i, choice) in question.choices.iter().enumerate() {
let marker = if Some(i) == question.default {
"*"
} else {
" "
};
println!(" {} [{}] {}", marker, i + 1, choice);
}
print!("> ");
let _ = io::stdout().flush();
let mut input = String::new();
let _ = io::stdin().read_line(&mut input);
let input = input.trim();
if let Ok(num) = input.parse::<usize>() {
if num >= 1 && num <= question.choices.len() {
return Answer {
text: question.choices[num - 1].clone(),
index: Some(num - 1),
};
}
}
Answer {
text: input.to_string(),
index: None,
}
}
}
pub struct AutoApproveInterviewer;
#[async_trait]
impl Interviewer for AutoApproveInterviewer {
async fn ask(&self, question: Question) -> Answer {
let (text, index) = if let Some(default) = question.default {
(question.choices[default].clone(), Some(default))
} else if !question.choices.is_empty() {
(question.choices[0].clone(), Some(0))
} else {
("yes".to_string(), None)
};
Answer { text, index }
}
}
pub struct QueueInterviewer {
answers: std::sync::Mutex<Vec<Answer>>,
}
impl QueueInterviewer {
pub fn new(answers: Vec<Answer>) -> Self {
Self {
answers: std::sync::Mutex::new(answers),
}
}
pub fn from_strings(answers: Vec<&str>) -> Self {
Self::new(
answers
.into_iter()
.map(|s| Answer {
text: s.to_string(),
index: None,
})
.collect(),
)
}
}
#[async_trait]
impl Interviewer for QueueInterviewer {
async fn ask(&self, question: Question) -> Answer {
let mut queue = self.answers.lock().unwrap();
if queue.is_empty() {
if !question.choices.is_empty() {
Answer {
text: question.choices[0].clone(),
index: Some(0),
}
} else {
Answer {
text: "yes".to_string(),
index: None,
}
}
} else {
queue.remove(0)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_auto_approve_first_choice() {
let interviewer = AutoApproveInterviewer;
let answer = interviewer
.ask(Question {
text: "Choose".into(),
choices: vec!["A".into(), "B".into()],
default: None,
})
.await;
assert_eq!(answer.text, "A");
assert_eq!(answer.index, Some(0));
}
#[tokio::test]
async fn test_auto_approve_default() {
let interviewer = AutoApproveInterviewer;
let answer = interviewer
.ask(Question {
text: "Choose".into(),
choices: vec!["A".into(), "B".into()],
default: Some(1),
})
.await;
assert_eq!(answer.text, "B");
assert_eq!(answer.index, Some(1));
}
#[tokio::test]
async fn test_queue_interviewer() {
let interviewer = QueueInterviewer::from_strings(vec!["first", "second"]);
let a1 = interviewer
.ask(Question {
text: "Q1?".into(),
choices: vec![],
default: None,
})
.await;
assert_eq!(a1.text, "first");
let a2 = interviewer
.ask(Question {
text: "Q2?".into(),
choices: vec![],
default: None,
})
.await;
assert_eq!(a2.text, "second");
}
#[tokio::test]
async fn test_queue_interviewer_fallback() {
let interviewer = QueueInterviewer::from_strings(vec![]);
let answer = interviewer
.ask(Question {
text: "Q?".into(),
choices: vec!["option".into()],
default: None,
})
.await;
assert_eq!(answer.text, "option");
}
}