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
35pub trait InterviewBackend {
37 fn execute(&self, interview: &Interview) -> Result<Answers, BackendError>;
39
40 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 let _ = validator;
49 self.execute(interview)
50 }
51}
52
53#[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 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 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}