derive_wizard_types/
interview.rs

1use crate::{AssumedAnswer, SuggestedAnswer};
2
3/// A sequence of sections, which contain questions.
4#[derive(Debug, Clone)]
5pub struct Interview {
6    pub sections: Vec<Question>,
7    pub prelude: Option<String>,
8    pub epilogue: Option<String>,
9}
10
11#[derive(Debug, Clone)]
12pub struct Question {
13    /// The unique identifier for the question.
14    id: Option<String>,
15
16    /// The field name.
17    name: String,
18
19    /// The prompt message to display.
20    prompt: String,
21
22    kind: QuestionKind,
23
24    /// An assumed answer that causes this question to be skipped.
25    assumed: Option<AssumedAnswer>,
26}
27
28impl Question {
29    /// Create a new question with the given id, name, prompt, and kind.
30    pub const fn new(id: Option<String>, name: String, prompt: String, kind: QuestionKind) -> Self {
31        Self {
32            id,
33            name,
34            prompt,
35            kind,
36            assumed: None,
37        }
38    }
39
40    pub fn id(&self) -> Option<&str> {
41        self.id.as_deref()
42    }
43
44    pub fn name(&self) -> &str {
45        &self.name
46    }
47
48    pub fn prompt(&self) -> &str {
49        &self.prompt
50    }
51
52    pub const fn kind(&self) -> &QuestionKind {
53        &self.kind
54    }
55
56    pub const fn kind_mut(&mut self) -> &mut QuestionKind {
57        &mut self.kind
58    }
59
60    pub const fn assumed(&self) -> Option<&AssumedAnswer> {
61        self.assumed.as_ref()
62    }
63
64    /// Set the suggested value for this question based on its kind.
65    pub fn set_suggestion(&mut self, value: impl Into<SuggestedAnswer>) {
66        match (&mut self.kind, value.into()) {
67            (QuestionKind::Input(q), SuggestedAnswer::String(v)) => {
68                q.default = Some(v);
69            }
70            (QuestionKind::Multiline(q), SuggestedAnswer::String(v)) => {
71                q.default = Some(v);
72            }
73            (QuestionKind::Int(q), SuggestedAnswer::Int(v)) => {
74                q.default = Some(v);
75            }
76            (QuestionKind::Float(q), SuggestedAnswer::Float(v)) => {
77                q.default = Some(v);
78            }
79            (QuestionKind::Confirm(q), SuggestedAnswer::Bool(v)) => {
80                q.default = v;
81            }
82            _ => {}
83        }
84    }
85
86    /// Set an assumed value for this question, causing it to be skipped in the interview.
87    pub fn set_assumption(&mut self, value: impl Into<AssumedAnswer>) {
88        self.assumed = Some(value.into());
89    }
90}
91
92/// Possible question kinds which a wizard may ask.
93#[derive(Debug, Clone)]
94pub enum QuestionKind {
95    /// A text input question for string values.
96    Input(InputQuestion),
97
98    /// A multi-line text input.
99    Multiline(MultilineQuestion),
100
101    /// A password/masked input question.
102    Masked(MaskedQuestion),
103
104    /// A number input question (integers).
105    Int(IntQuestion),
106
107    /// A number input question (floating point).
108    Float(FloatQuestion),
109
110    /// A yes/no confirmation question.
111    Confirm(ConfirmQuestion),
112
113    /// A multi-select question where users can pick multiple options.
114    MultiSelect(MultiSelectQuestion),
115
116    Sequence(Vec<Question>),
117
118    Alternative(usize, Vec<Question>),
119}
120
121impl QuestionKind {
122    /// Check if this is a Sequence containing only Alternative items (i.e., enum variants).
123    pub fn is_enum_alternatives(&self) -> bool {
124        matches!(self, Self::Sequence(questions)
125            if !questions.is_empty()
126                && questions.iter().all(|q| matches!(q.kind(), Self::Alternative(_, _))))
127    }
128}
129
130/// Configuration for a text input question.
131#[derive(Debug, Clone)]
132pub struct InputQuestion {
133    /// Optional default value.
134    pub default: Option<String>,
135
136    /// Validation function name.
137    pub validate: Option<String>,
138}
139
140/// Configuration for a multi-line text editor question.
141#[derive(Debug, Clone)]
142pub struct MultilineQuestion {
143    /// Optional default value.
144    pub default: Option<String>,
145
146    /// Validation function name.
147    pub validate: Option<String>,
148}
149
150/// Configuration for a password/masked input question.
151#[derive(Debug, Clone)]
152pub struct MaskedQuestion {
153    /// The masking character (default: '*').
154    pub mask: Option<char>,
155
156    /// Validation function name.
157    pub validate: Option<String>,
158}
159
160/// Configuration for an integer input question.
161#[derive(Debug, Clone)]
162pub struct IntQuestion {
163    /// Optional default value
164    pub default: Option<i64>,
165
166    /// Optional minimum value
167    pub min: Option<i64>,
168
169    /// Optional maximum value
170    pub max: Option<i64>,
171
172    /// Validation function name.
173    pub validate: Option<String>,
174}
175
176/// Configuration for a floating-point input question.
177#[derive(Debug, Clone)]
178pub struct FloatQuestion {
179    /// Optional default value.
180    pub default: Option<f64>,
181
182    /// Optional minimum value
183    pub min: Option<f64>,
184
185    /// Optional maximum value
186    pub max: Option<f64>,
187
188    /// Validation function name.
189    pub validate: Option<String>,
190}
191
192/// Configuration for a yes/no confirmation question.
193#[derive(Debug, Clone)]
194pub struct ConfirmQuestion {
195    /// Default value (true for yes, false for no)
196    pub default: bool,
197}
198
199/// Configuration for a multi-select question.
200#[derive(Debug, Clone)]
201pub struct MultiSelectQuestion {
202    /// The options to choose from.
203    pub options: Vec<String>,
204
205    /// Default selected indices.
206    pub defaults: Vec<usize>,
207}