use async_trait::async_trait;
use inquire::InquireError;
use crate::error::WizardError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StepOutcome {
Next,
Back,
}
#[async_trait]
pub trait WizardStep<S>: Send + Sync {
fn name(&self) -> &'static str;
async fn prompt(&self, state: &mut S) -> Result<StepOutcome, InquireError>;
}
pub struct Wizard<S> {
initial: S,
steps: Vec<Box<dyn WizardStep<S>>>,
}
impl<S: Send + 'static> Wizard<S> {
#[must_use]
pub fn builder() -> WizardBuilder<S> {
WizardBuilder { initial: None, steps: Vec::new() }
}
pub async fn run(self) -> Result<S, WizardError> {
let Self { mut initial, steps } = self;
if steps.is_empty() {
return Ok(initial);
}
let mut idx = 0usize;
loop {
let step = &steps[idx];
match step.prompt(&mut initial).await {
Ok(StepOutcome::Next) => {
idx += 1;
if idx >= steps.len() {
return Ok(initial);
}
}
Ok(StepOutcome::Back) | Err(InquireError::OperationCanceled) => {
if idx == 0 {
return Err(WizardError::Cancelled);
}
idx -= 1;
}
Err(InquireError::OperationInterrupted) => {
return Err(WizardError::Interrupted);
}
Err(other) => {
return Err(WizardError::Step {
step: step.name().to_string(),
message: other.to_string(),
});
}
}
}
}
}
pub struct WizardBuilder<S> {
initial: Option<S>,
steps: Vec<Box<dyn WizardStep<S>>>,
}
impl<S: Send + 'static> WizardBuilder<S> {
#[must_use]
pub fn initial(mut self, state: S) -> Self {
self.initial = Some(state);
self
}
#[must_use]
pub fn step<W>(mut self, step: W) -> Self
where
W: WizardStep<S> + 'static,
{
self.steps.push(Box::new(step));
self
}
#[must_use]
pub fn build(self) -> Wizard<S> {
Wizard {
initial: self.initial.expect("Wizard::builder requires .initial(...)"),
steps: self.steps,
}
}
}