1use crate::interview::{Interview, Section};
2use std::collections::HashMap;
3
4#[cfg(feature = "requestty-backend")]
5pub mod requestty_backend;
6
7#[cfg(feature = "dialoguer-backend")]
8pub mod dialoguer_backend;
9
10#[cfg(feature = "egui-backend")]
11pub mod egui_backend;
12
13#[derive(Debug, Clone, Default)]
15pub struct Answers {
16 values: HashMap<String, AnswerValue>,
17}
18
19#[derive(Debug, Clone)]
21pub enum AnswerValue {
22 String(String),
23 Int(i64),
24 Float(f64),
25 Bool(bool),
26 Nested(Box<Answers>),
27}
28
29impl Answers {
30 pub fn new() -> Self {
31 Self {
32 values: HashMap::new(),
33 }
34 }
35
36 pub fn insert(&mut self, key: String, value: AnswerValue) {
37 self.values.insert(key, value);
38 }
39
40 pub fn get(&self, key: &str) -> Option<&AnswerValue> {
41 self.values.get(key)
42 }
43
44 pub fn merge(&mut self, other: Answers) {
45 self.values.extend(other.values);
46 }
47
48 pub fn as_string(&self, key: &str) -> Result<String, AnswerError> {
49 match self.get(key) {
50 Some(AnswerValue::String(s)) => Ok(s.clone()),
51 Some(_) => Err(AnswerError::TypeMismatch {
52 key: key.to_string(),
53 expected: "String",
54 }),
55 None => Err(AnswerError::MissingKey(key.to_string())),
56 }
57 }
58
59 pub fn as_int(&self, key: &str) -> Result<i64, AnswerError> {
60 match self.get(key) {
61 Some(AnswerValue::Int(i)) => Ok(*i),
62 Some(_) => Err(AnswerError::TypeMismatch {
63 key: key.to_string(),
64 expected: "Int",
65 }),
66 None => Err(AnswerError::MissingKey(key.to_string())),
67 }
68 }
69
70 pub fn as_float(&self, key: &str) -> Result<f64, AnswerError> {
71 match self.get(key) {
72 Some(AnswerValue::Float(f)) => Ok(*f),
73 Some(_) => Err(AnswerError::TypeMismatch {
74 key: key.to_string(),
75 expected: "Float",
76 }),
77 None => Err(AnswerError::MissingKey(key.to_string())),
78 }
79 }
80
81 pub fn as_bool(&self, key: &str) -> Result<bool, AnswerError> {
82 match self.get(key) {
83 Some(AnswerValue::Bool(b)) => Ok(*b),
84 Some(_) => Err(AnswerError::TypeMismatch {
85 key: key.to_string(),
86 expected: "Bool",
87 }),
88 None => Err(AnswerError::MissingKey(key.to_string())),
89 }
90 }
91
92 pub fn as_nested(&self, key: &str) -> Result<&Answers, AnswerError> {
93 match self.get(key) {
94 Some(AnswerValue::Nested(nested)) => Ok(nested),
95 Some(_) => Err(AnswerError::TypeMismatch {
96 key: key.to_string(),
97 expected: "Nested",
98 }),
99 None => Err(AnswerError::MissingKey(key.to_string())),
100 }
101 }
102}
103
104#[derive(Debug, thiserror::Error)]
105pub enum AnswerError {
106 #[error("Missing key: {0}")]
107 MissingKey(String),
108
109 #[error("Type mismatch for key '{key}': expected {expected}")]
110 TypeMismatch { key: String, expected: &'static str },
111}
112
113#[derive(Debug, thiserror::Error)]
114pub enum BackendError {
115 #[error("Answer error: {0}")]
116 Answer(#[from] AnswerError),
117
118 #[error("Validation error: {0}")]
119 Validation(String),
120
121 #[error("Execution error: {0}")]
122 ExecutionError(String),
123
124 #[error("IO error: {0}")]
125 Io(#[from] std::io::Error),
126
127 #[error("Backend-specific error: {0}")]
128 Custom(String),
129}
130
131pub trait InterviewBackend {
133 fn execute(&self, interview: &Interview) -> Result<Answers, BackendError>;
135
136 fn execute_section(&self, section: &Section) -> Result<Answers, BackendError> {
138 let _ = section;
140 Err(BackendError::Custom(
141 "execute_section not implemented".to_string(),
142 ))
143 }
144}
145
146#[derive(Debug, Default)]
148pub struct TestBackend {
149 answers: Answers,
150}
151
152impl TestBackend {
153 pub fn new() -> Self {
154 Self::default()
155 }
156
157 pub fn with_string(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
158 self.answers
159 .insert(key.into(), AnswerValue::String(value.into()));
160 self
161 }
162
163 pub fn with_int(mut self, key: impl Into<String>, value: i64) -> Self {
164 self.answers.insert(key.into(), AnswerValue::Int(value));
165 self
166 }
167
168 pub fn with_float(mut self, key: impl Into<String>, value: f64) -> Self {
169 self.answers.insert(key.into(), AnswerValue::Float(value));
170 self
171 }
172
173 pub fn with_bool(mut self, key: impl Into<String>, value: bool) -> Self {
174 self.answers.insert(key.into(), AnswerValue::Bool(value));
175 self
176 }
177}
178
179impl InterviewBackend for TestBackend {
180 fn execute(&self, _interview: &Interview) -> Result<Answers, BackendError> {
181 Ok(self.answers.clone())
183 }
184}