derive_wizard/
backend.rs

1use std::slice;
2
3use crate::{AnswerError, AnswerValue, Answers, interview::Interview};
4
5#[cfg(feature = "requestty-backend")]
6pub mod requestty_backend;
7
8#[cfg(feature = "dialoguer-backend")]
9pub mod dialoguer_backend;
10
11#[cfg(feature = "egui-backend")]
12pub mod egui_backend;
13
14#[cfg(feature = "ratatui-backend")]
15pub mod ratatui_backend;
16
17#[derive(Debug, thiserror::Error)]
18pub enum BackendError {
19    #[error("Answer error: {0}")]
20    Answer(#[from] AnswerError),
21
22    #[error("Validation error: {0}")]
23    Validation(String),
24
25    #[error("Execution error: {0}")]
26    ExecutionError(String),
27
28    #[error("IO error: {0}")]
29    Io(#[from] std::io::Error),
30
31    #[error("Backend-specific error: {0}")]
32    Custom(String),
33}
34
35/// Trait for interview execution backends
36pub trait InterviewBackend {
37    /// Execute an interview and return the collected answers
38    fn execute(&self, interview: &Interview) -> Result<Answers, BackendError>;
39
40    /// Execute an interview with validation support
41    /// The validator function takes (field_name, value, answers) and returns validation result
42    fn execute_with_validator(
43        &self,
44        interview: &Interview,
45        validator: &(dyn Fn(&str, &str, &Answers) -> Result<(), String> + Send + Sync),
46    ) -> Result<Answers, BackendError> {
47        // Default implementation: just execute without validation
48        let _ = validator;
49        self.execute(interview)
50    }
51}
52
53/// Test backend that returns predefined answers
54#[derive(Debug, Default)]
55pub struct TestBackend {
56    answers: Answers,
57}
58
59impl TestBackend {
60    pub fn new() -> Self {
61        Self::default()
62    }
63
64    pub fn with_string(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
65        self.answers
66            .insert(key.into(), AnswerValue::String(value.into()));
67        self
68    }
69
70    pub fn with_int(mut self, key: impl Into<String>, value: i64) -> Self {
71        self.answers.insert(key.into(), AnswerValue::Int(value));
72        self
73    }
74
75    pub fn with_float(mut self, key: impl Into<String>, value: f64) -> Self {
76        self.answers.insert(key.into(), AnswerValue::Float(value));
77        self
78    }
79
80    pub fn with_bool(mut self, key: impl Into<String>, value: bool) -> Self {
81        self.answers.insert(key.into(), AnswerValue::Bool(value));
82        self
83    }
84}
85
86impl InterviewBackend for TestBackend {
87    fn execute(&self, interview: &Interview) -> Result<Answers, BackendError> {
88        use crate::interview::{Question, QuestionKind};
89
90        let mut answers = self.answers.clone();
91
92        // Recursively add assumed answers from the interview
93        fn collect_assumptions(questions: &[Question], answers: &mut Answers) {
94            for question in questions {
95                if let Some(assumed) = question.assumed() {
96                    answers.insert(question.name().to_string(), assumed.into());
97                }
98
99                // Recursively handle nested questions
100                match question.kind() {
101                    QuestionKind::Sequence(nested_questions) => {
102                        collect_assumptions(nested_questions, answers);
103                    }
104                    QuestionKind::Alternative(_, alternatives) => {
105                        for alt in alternatives {
106                            let alt = slice::from_ref(alt);
107                            collect_assumptions(alt, answers);
108                        }
109                    }
110                    _ => {}
111                }
112            }
113        }
114
115        collect_assumptions(&interview.sections, &mut answers);
116
117        Ok(answers)
118    }
119}