elicitor_types/
question.rs

1use crate::{DefaultValue, ResponsePath, ResponseValue};
2
3/// A single question in a survey.
4#[derive(Debug, Clone, PartialEq)]
5pub struct Question {
6    /// The path to this question's response in the Responses map.
7    path: ResponsePath,
8
9    /// The prompt text shown to the user.
10    ask: String,
11
12    /// The kind of question (determines input type and nested structure).
13    kind: QuestionKind,
14
15    /// Default value for this question (none, suggested, or assumed).
16    default: DefaultValue,
17}
18
19impl Question {
20    /// Create a new question.
21    pub fn new(path: impl Into<ResponsePath>, ask: impl Into<String>, kind: QuestionKind) -> Self {
22        Self {
23            path: path.into(),
24            ask: ask.into(),
25            kind,
26            default: DefaultValue::None,
27        }
28    }
29
30    /// Get the response path for this question.
31    pub fn path(&self) -> &ResponsePath {
32        &self.path
33    }
34
35    /// Get the prompt text.
36    pub fn ask(&self) -> &str {
37        &self.ask
38    }
39
40    /// Get the question kind.
41    pub fn kind(&self) -> &QuestionKind {
42        &self.kind
43    }
44
45    /// Get a mutable reference to the question kind.
46    pub fn kind_mut(&mut self) -> &mut QuestionKind {
47        &mut self.kind
48    }
49
50    /// Get the default value.
51    pub fn default(&self) -> &DefaultValue {
52        &self.default
53    }
54
55    /// Set a suggested default value (user can modify).
56    pub fn set_suggestion(&mut self, value: impl Into<ResponseValue>) {
57        self.default = DefaultValue::Suggested(value.into());
58    }
59
60    /// Set an assumed value (question is skipped entirely).
61    pub fn set_assumption(&mut self, value: impl Into<ResponseValue>) {
62        self.default = DefaultValue::Assumed(value.into());
63    }
64
65    /// Clear any default value.
66    pub fn clear_default(&mut self) {
67        self.default = DefaultValue::None;
68    }
69
70    /// Check if this question should be skipped (has an assumed value).
71    pub fn is_assumed(&self) -> bool {
72        self.default.is_assumed()
73    }
74}
75
76/// The kind of question, determining input type and structure.
77#[derive(Debug, Clone, PartialEq)]
78pub enum QuestionKind {
79    /// No data to collect (unit enum variants, unit structs).
80    Unit,
81
82    /// Single-line text input.
83    Input(InputQuestion),
84
85    /// Multi-line text input (opens editor or textarea).
86    Multiline(MultilineQuestion),
87
88    /// Masked input for passwords.
89    Masked(MaskedQuestion),
90
91    /// Integer input with optional min/max bounds.
92    Int(IntQuestion),
93
94    /// Floating-point input with optional min/max bounds.
95    Float(FloatQuestion),
96
97    /// Yes/no confirmation.
98    Confirm(ConfirmQuestion),
99
100    /// List of values (Vec<T> where T is a primitive type).
101    List(ListQuestion),
102
103    /// Select any number of options from a list (Vec<Enum>).
104    AnyOf(AnyOfQuestion),
105
106    /// A group of questions — answer all (nested structs, struct variants).
107    AllOf(AllOfQuestion),
108
109    /// Choose one variant — pick one, then answer its questions (enums).
110    OneOf(OneOfQuestion),
111}
112
113impl QuestionKind {
114    /// Check if this is a Unit kind (no data to collect).
115    pub fn is_unit(&self) -> bool {
116        self == &Self::Unit
117    }
118
119    pub fn is_basic(&self) -> bool {
120        matches!(
121            self,
122            Self::Input(_)
123                | Self::Multiline(_)
124                | Self::Masked(_)
125                | Self::Int(_)
126                | Self::Float(_)
127                | Self::Confirm(_)
128                | Self::List(_)
129        )
130    }
131
132    /// Check if this is a structural kind (AllOf, OneOf, AnyOf).
133    pub fn is_structural(&self) -> bool {
134        matches!(self, Self::AllOf(_) | Self::OneOf(_) | Self::AnyOf(_))
135    }
136}
137
138/// A variant in a OneOf question (enum variant).
139#[derive(Debug, Clone, PartialEq)]
140pub struct Variant {
141    /// Variant name for display (e.g., "Male", "Female", "Other").
142    pub name: String,
143
144    /// What to collect for this variant.
145    /// - Unit for unit variants (no data)
146    /// - Input for newtype variants with String
147    /// - AllOf for struct variants
148    /// - OneOf for nested enums
149    pub kind: QuestionKind,
150}
151
152impl Variant {
153    /// Create a new variant with the given name and kind.
154    pub fn new(name: impl Into<String>, kind: QuestionKind) -> Self {
155        Self {
156            name: name.into(),
157            kind,
158        }
159    }
160
161    /// Create a unit variant (no data to collect).
162    pub fn unit(name: impl Into<String>) -> Self {
163        Self::new(name, QuestionKind::Unit)
164    }
165}
166
167/// Configuration for an AnyOf question (multi-select with potential follow-up questions).
168#[derive(Debug, Clone, PartialEq)]
169pub struct AnyOfQuestion {
170    /// The available variants to choose from.
171    pub variants: Vec<Variant>,
172
173    /// Default selected indices (if any).
174    pub defaults: Vec<usize>,
175}
176
177impl AnyOfQuestion {
178    /// Create a new AnyOf question with the given variants.
179    pub fn new(variants: Vec<Variant>) -> Self {
180        Self {
181            variants,
182            defaults: Vec::new(),
183        }
184    }
185
186    /// Create with default selections.
187    pub fn with_defaults(variants: Vec<Variant>, defaults: Vec<usize>) -> Self {
188        Self { variants, defaults }
189    }
190}
191
192/// Configuration for an AllOf question (a group of questions that are all answered).
193///
194/// Used for nested structs and struct enum variants.
195#[derive(Debug, Clone, PartialEq)]
196pub struct AllOfQuestion {
197    /// The questions in this group.
198    pub questions: Vec<Question>,
199}
200
201impl AllOfQuestion {
202    /// Create a new AllOf question with the given questions.
203    pub fn new(questions: Vec<Question>) -> Self {
204        Self { questions }
205    }
206
207    /// Create an empty AllOf question.
208    pub fn empty() -> Self {
209        Self {
210            questions: Vec::new(),
211        }
212    }
213
214    /// Get the questions.
215    pub fn questions(&self) -> &[Question] {
216        &self.questions
217    }
218
219    /// Get a mutable reference to the questions.
220    pub fn questions_mut(&mut self) -> &mut Vec<Question> {
221        &mut self.questions
222    }
223}
224
225/// Configuration for a OneOf question (choose exactly one variant).
226///
227/// Used for enums where the user selects one variant, then answers
228/// any follow-up questions for that variant.
229#[derive(Debug, Clone, PartialEq)]
230pub struct OneOfQuestion {
231    /// The available variants to choose from.
232    pub variants: Vec<Variant>,
233
234    /// Default selected variant index (if any).
235    pub default: Option<usize>,
236}
237
238impl OneOfQuestion {
239    /// Create a new OneOf question with the given variants.
240    pub fn new(variants: Vec<Variant>) -> Self {
241        Self {
242            variants,
243            default: None,
244        }
245    }
246
247    /// Create with a default selection.
248    pub fn with_default(variants: Vec<Variant>, default: usize) -> Self {
249        Self {
250            variants,
251            default: Some(default),
252        }
253    }
254
255    /// Get the variants.
256    pub fn variants(&self) -> &[Variant] {
257        &self.variants
258    }
259
260    /// Get a mutable reference to the variants.
261    pub fn variants_mut(&mut self) -> &mut Vec<Variant> {
262        &mut self.variants
263    }
264}
265
266/// Configuration for a text input question.
267#[derive(Debug, Clone, Default, PartialEq)]
268pub struct InputQuestion {
269    /// Optional default value.
270    pub default: Option<String>,
271
272    /// Validation function name (resolved at compile time).
273    pub validate: Option<String>,
274}
275
276impl InputQuestion {
277    /// Create a new input question.
278    pub fn new() -> Self {
279        Self::default()
280    }
281
282    /// Create with a default value.
283    pub fn with_default(default: impl Into<String>) -> Self {
284        Self {
285            default: Some(default.into()),
286            validate: None,
287        }
288    }
289
290    /// Create with a validator.
291    pub fn with_validator(validate: Option<String>) -> Self {
292        Self {
293            default: None,
294            validate,
295        }
296    }
297}
298
299/// Configuration for a multi-line text editor question.
300#[derive(Debug, Clone, Default, PartialEq)]
301pub struct MultilineQuestion {
302    /// Optional default value.
303    pub default: Option<String>,
304
305    /// Validation function name.
306    pub validate: Option<String>,
307}
308
309impl MultilineQuestion {
310    /// Create a new multiline question.
311    pub fn new() -> Self {
312        Self::default()
313    }
314
315    /// Create with a validator.
316    pub fn with_validator(validate: Option<String>) -> Self {
317        Self {
318            default: None,
319            validate,
320        }
321    }
322}
323
324/// Configuration for a password/masked input question.
325#[derive(Debug, Clone, Default, PartialEq)]
326pub struct MaskedQuestion {
327    /// The masking character (default: '*').
328    pub mask: Option<char>,
329
330    /// Validation function name.
331    pub validate: Option<String>,
332}
333
334impl MaskedQuestion {
335    /// Create a new masked question.
336    pub fn new() -> Self {
337        Self::default()
338    }
339
340    /// Create with a custom mask character.
341    pub fn with_mask(mask: char) -> Self {
342        Self {
343            mask: Some(mask),
344            validate: None,
345        }
346    }
347
348    /// Create with a validator.
349    pub fn with_validator(validate: Option<String>) -> Self {
350        Self {
351            mask: None,
352            validate,
353        }
354    }
355}
356
357/// Configuration for an integer input question.
358#[derive(Debug, Clone, Default, PartialEq)]
359pub struct IntQuestion {
360    /// Optional default value.
361    pub default: Option<i64>,
362
363    /// Optional minimum value.
364    pub min: Option<i64>,
365
366    /// Optional maximum value.
367    pub max: Option<i64>,
368
369    /// Validation function name.
370    pub validate: Option<String>,
371}
372
373impl IntQuestion {
374    /// Create a new integer question.
375    pub fn new() -> Self {
376        Self::default()
377    }
378
379    /// Create with bounds.
380    pub fn with_bounds(min: Option<i64>, max: Option<i64>) -> Self {
381        Self {
382            default: None,
383            min,
384            max,
385            validate: None,
386        }
387    }
388
389    /// Create with bounds and a validator.
390    pub fn with_bounds_and_validator(
391        min: Option<i64>,
392        max: Option<i64>,
393        validate: Option<String>,
394    ) -> Self {
395        Self {
396            default: None,
397            min,
398            max,
399            validate,
400        }
401    }
402}
403
404/// Configuration for a floating-point input question.
405#[derive(Debug, Clone, Default, PartialEq)]
406pub struct FloatQuestion {
407    /// Optional default value.
408    pub default: Option<f64>,
409
410    /// Optional minimum value.
411    pub min: Option<f64>,
412
413    /// Optional maximum value.
414    pub max: Option<f64>,
415
416    /// Validation function name.
417    pub validate: Option<String>,
418}
419
420impl FloatQuestion {
421    /// Create a new float question.
422    pub fn new() -> Self {
423        Self::default()
424    }
425
426    /// Create with bounds.
427    pub fn with_bounds(min: Option<f64>, max: Option<f64>) -> Self {
428        Self {
429            default: None,
430            min,
431            max,
432            validate: None,
433        }
434    }
435
436    /// Create with bounds and a validator.
437    pub fn with_bounds_and_validator(
438        min: Option<f64>,
439        max: Option<f64>,
440        validate: Option<String>,
441    ) -> Self {
442        Self {
443            default: None,
444            min,
445            max,
446            validate,
447        }
448    }
449}
450
451/// Configuration for a yes/no confirmation question.
452#[derive(Debug, Clone, Default, PartialEq)]
453pub struct ConfirmQuestion {
454    /// Default value (true for yes, false for no).
455    pub default: bool,
456}
457
458impl ConfirmQuestion {
459    /// Create a new confirm question with default false.
460    pub fn new() -> Self {
461        Self::default()
462    }
463
464    /// Create with a default value.
465    pub fn with_default(default: bool) -> Self {
466        Self { default }
467    }
468}
469
470/// The type of elements in a list question.
471#[derive(Debug, Clone, PartialEq, Default)]
472pub enum ListElementKind {
473    /// String elements.
474    #[default]
475    String,
476    /// Integer elements with optional bounds.
477    Int { min: Option<i64>, max: Option<i64> },
478    /// Float elements with optional bounds.
479    Float { min: Option<f64>, max: Option<f64> },
480}
481
482/// Configuration for a list input question (Vec<T>).
483///
484/// Allows collecting multiple values of the same type.
485#[derive(Debug, Clone, Default, PartialEq)]
486pub struct ListQuestion {
487    /// The type of elements in the list.
488    pub element_kind: ListElementKind,
489
490    /// Optional minimum number of elements.
491    pub min_items: Option<usize>,
492
493    /// Optional maximum number of elements.
494    pub max_items: Option<usize>,
495
496    /// Validation function name.
497    pub validate: Option<String>,
498}
499
500impl ListQuestion {
501    /// Create a new list question for strings.
502    pub fn new() -> Self {
503        Self::default()
504    }
505
506    /// Create a list question for strings.
507    pub fn strings() -> Self {
508        Self {
509            element_kind: ListElementKind::String,
510            ..Default::default()
511        }
512    }
513
514    /// Create a list question for integers.
515    pub fn ints() -> Self {
516        Self {
517            element_kind: ListElementKind::Int {
518                min: None,
519                max: None,
520            },
521            ..Default::default()
522        }
523    }
524
525    /// Create a list question for integers with bounds.
526    pub fn ints_with_bounds(min: Option<i64>, max: Option<i64>) -> Self {
527        Self {
528            element_kind: ListElementKind::Int { min, max },
529            ..Default::default()
530        }
531    }
532
533    /// Create a list question for floats.
534    pub fn floats() -> Self {
535        Self {
536            element_kind: ListElementKind::Float {
537                min: None,
538                max: None,
539            },
540            ..Default::default()
541        }
542    }
543
544    /// Create a list question for floats with bounds.
545    pub fn floats_with_bounds(min: Option<f64>, max: Option<f64>) -> Self {
546        Self {
547            element_kind: ListElementKind::Float { min, max },
548            ..Default::default()
549        }
550    }
551
552    /// Set item count constraints.
553    pub fn with_item_bounds(mut self, min: Option<usize>, max: Option<usize>) -> Self {
554        self.min_items = min;
555        self.max_items = max;
556        self
557    }
558
559    /// Set a validator function.
560    pub fn with_validator(mut self, validate: impl Into<String>) -> Self {
561        self.validate = Some(validate.into());
562        self
563    }
564}
565
566/// The key suffix used to store the selected enum variant index in responses.
567/// For a field "method", the selection is stored at "method.selected_variant".
568pub const SELECTED_VARIANT_KEY: &str = "selected_variant";
569
570/// The key suffix used to store selected variant indices for AnyOf questions.
571/// For a field "features", the selections are stored at "features.selected_variants".
572pub const SELECTED_VARIANTS_KEY: &str = "selected_variants";