1use std::{error::Error, fmt, rc::Rc};
2
3use gpui::{Entity, SharedString};
4
5use crate::input::InputState;
6
7pub type QuestionnaireValidator =
9 Rc<dyn Fn(&QuestionnaireValidationContext) -> Result<(), SharedString> + 'static>;
10
11#[derive(Clone, Debug)]
13pub struct QuestionnaireChoiceDefinition {
14 value: SharedString,
15 accessibility_label: SharedString,
16 description: Option<SharedString>,
17 disabled: bool,
18 default_selected: bool,
19}
20
21impl QuestionnaireChoiceDefinition {
22 pub fn new(
23 value: impl Into<SharedString>,
24 accessibility_label: impl Into<SharedString>,
25 ) -> Self {
26 Self {
27 value: value.into(),
28 accessibility_label: accessibility_label.into(),
29 description: None,
30 disabled: false,
31 default_selected: false,
32 }
33 }
34
35 pub fn with_description(mut self, description: impl Into<SharedString>) -> Self {
36 self.description = Some(description.into());
37 self
38 }
39
40 pub fn with_disabled(mut self, disabled: bool) -> Self {
41 self.disabled = disabled;
42 self
43 }
44
45 pub fn with_default_selected(mut self, selected: bool) -> Self {
46 self.default_selected = selected;
47 self
48 }
49
50 pub fn value(&self) -> &SharedString {
51 &self.value
52 }
53
54 pub fn accessibility_label(&self) -> &SharedString {
55 &self.accessibility_label
56 }
57
58 pub fn description(&self) -> Option<&SharedString> {
59 self.description.as_ref()
60 }
61
62 pub fn is_disabled(&self) -> bool {
63 self.disabled
64 }
65
66 pub fn is_default_selected(&self) -> bool {
67 self.default_selected
68 }
69}
70
71#[derive(Clone, Debug)]
73pub struct QuestionnaireInputDefinition {
74 state: Entity<InputState>,
75 accessibility_label: SharedString,
76 disabled: bool,
77}
78
79impl QuestionnaireInputDefinition {
80 pub fn new(state: Entity<InputState>, accessibility_label: impl Into<SharedString>) -> Self {
81 Self {
82 state,
83 accessibility_label: accessibility_label.into(),
84 disabled: false,
85 }
86 }
87
88 pub fn with_disabled(mut self, disabled: bool) -> Self {
89 self.disabled = disabled;
90 self
91 }
92
93 pub fn state(&self) -> &Entity<InputState> {
94 &self.state
95 }
96
97 pub fn accessibility_label(&self) -> &SharedString {
98 &self.accessibility_label
99 }
100
101 pub fn is_disabled(&self) -> bool {
102 self.disabled
103 }
104}
105
106#[derive(Clone)]
108pub struct QuestionnaireItemDefinition {
109 name: SharedString,
110 accessibility_label: SharedString,
111 description: Option<SharedString>,
112 required: bool,
113 multiple: bool,
114 disabled: bool,
115 choices: Vec<QuestionnaireChoiceDefinition>,
116 input: Option<QuestionnaireInputDefinition>,
117 validator: Option<QuestionnaireValidator>,
118}
119
120impl fmt::Debug for QuestionnaireItemDefinition {
121 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
122 formatter
123 .debug_struct("QuestionnaireItemDefinition")
124 .field("name", &self.name)
125 .field("accessibility_label", &self.accessibility_label)
126 .field("description", &self.description)
127 .field("required", &self.required)
128 .field("multiple", &self.multiple)
129 .field("disabled", &self.disabled)
130 .field("choices", &self.choices)
131 .field("input", &self.input)
132 .field("validator", &self.validator.as_ref().map(|_| "<validator>"))
133 .finish()
134 }
135}
136
137impl QuestionnaireItemDefinition {
138 pub fn new(
139 name: impl Into<SharedString>,
140 accessibility_label: impl Into<SharedString>,
141 ) -> Self {
142 Self {
143 name: name.into(),
144 accessibility_label: accessibility_label.into(),
145 description: None,
146 required: false,
147 multiple: false,
148 disabled: false,
149 choices: Vec::new(),
150 input: None,
151 validator: None,
152 }
153 }
154
155 pub fn with_description(mut self, description: impl Into<SharedString>) -> Self {
156 self.description = Some(description.into());
157 self
158 }
159
160 pub fn with_required(mut self, required: bool) -> Self {
161 self.required = required;
162 self
163 }
164
165 pub fn with_multiple(mut self, multiple: bool) -> Self {
166 self.multiple = multiple;
167 self
168 }
169
170 pub fn with_disabled(mut self, disabled: bool) -> Self {
171 self.disabled = disabled;
172 self
173 }
174
175 pub fn with_choices(
176 mut self,
177 choices: impl IntoIterator<Item = QuestionnaireChoiceDefinition>,
178 ) -> Self {
179 self.choices = choices.into_iter().collect();
180 self
181 }
182
183 pub fn with_choice(mut self, choice: QuestionnaireChoiceDefinition) -> Self {
184 self.choices.push(choice);
185 self
186 }
187
188 pub fn with_input(mut self, input: QuestionnaireInputDefinition) -> Self {
189 self.input = Some(input);
190 self
191 }
192
193 pub fn with_validator(
194 mut self,
195 validator: impl Fn(&QuestionnaireValidationContext) -> Result<(), SharedString> + 'static,
196 ) -> Self {
197 self.validator = Some(Rc::new(validator));
198 self
199 }
200
201 pub fn name(&self) -> &SharedString {
202 &self.name
203 }
204
205 pub fn accessibility_label(&self) -> &SharedString {
206 &self.accessibility_label
207 }
208
209 pub fn description(&self) -> Option<&SharedString> {
210 self.description.as_ref()
211 }
212
213 pub fn is_required(&self) -> bool {
214 self.required
215 }
216
217 pub fn is_multiple(&self) -> bool {
218 self.multiple
219 }
220
221 pub fn is_disabled(&self) -> bool {
222 self.disabled
223 }
224
225 pub fn choices(&self) -> &[QuestionnaireChoiceDefinition] {
226 &self.choices
227 }
228
229 pub fn input(&self) -> Option<&QuestionnaireInputDefinition> {
230 self.input.as_ref()
231 }
232
233 pub fn validator(&self) -> Option<&QuestionnaireValidator> {
234 self.validator.as_ref()
235 }
236}
237
238#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
239pub enum QuestionnaireItemStatus {
240 #[default]
241 Unanswered,
242 Answered,
243 Skipped,
244}
245
246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
247pub enum QuestionnaireShortcutMode {
248 Letters,
249 Numbers,
250}
251
252#[derive(Clone, Debug, Default, PartialEq, Eq)]
254pub struct QuestionnaireAnswer {
255 pub(crate) choices: Vec<SharedString>,
256 pub(crate) freeform: Option<SharedString>,
257}
258
259impl QuestionnaireAnswer {
260 pub fn new() -> Self {
261 Self::default()
262 }
263
264 pub fn with_choices(
265 mut self,
266 choices: impl IntoIterator<Item = impl Into<SharedString>>,
267 ) -> Self {
268 self.choices.clear();
269 for choice in choices.into_iter().map(Into::into) {
270 if !self.choices.contains(&choice) {
271 self.choices.push(choice);
272 }
273 }
274 self
275 }
276
277 pub fn with_freeform(mut self, value: impl Into<SharedString>) -> Self {
278 let value = value.into();
279 self.freeform = (!value.trim().is_empty()).then_some(value);
280 self
281 }
282
283 pub fn choices(&self) -> &[SharedString] {
284 &self.choices
285 }
286
287 pub fn freeform(&self) -> Option<&SharedString> {
288 self.freeform.as_ref()
289 }
290
291 pub fn is_empty(&self) -> bool {
292 self.choices.is_empty() && self.freeform.is_none()
293 }
294}
295
296#[derive(Clone, Debug, Default, PartialEq, Eq)]
297pub struct QuestionnaireAnswers {
298 entries: Vec<(SharedString, QuestionnaireAnswer)>,
299}
300
301impl QuestionnaireAnswers {
302 pub(crate) fn from_entries(entries: Vec<(SharedString, QuestionnaireAnswer)>) -> Self {
303 Self { entries }
304 }
305
306 pub fn get(&self, name: &str) -> Option<&QuestionnaireAnswer> {
307 self.entries
308 .iter()
309 .find_map(|(item, answer)| (item.as_ref() == name).then_some(answer))
310 }
311
312 pub fn iter(&self) -> impl Iterator<Item = (&SharedString, &QuestionnaireAnswer)> {
313 self.entries.iter().map(|(name, answer)| (name, answer))
314 }
315
316 pub fn len(&self) -> usize {
317 self.entries.len()
318 }
319
320 pub fn is_empty(&self) -> bool {
321 self.entries.is_empty()
322 }
323}
324
325#[derive(Clone, Debug, PartialEq, Eq)]
326pub struct QuestionnaireProgressState {
327 current: usize,
328 total: usize,
329}
330
331impl QuestionnaireProgressState {
332 pub(crate) fn new(current: usize, total: usize) -> Self {
333 Self { current, total }
334 }
335
336 pub fn current(&self) -> usize {
337 self.current
338 }
339
340 pub fn total(&self) -> usize {
341 self.total
342 }
343}
344
345#[derive(Clone, Debug, PartialEq, Eq)]
346pub struct QuestionnaireItemState {
347 name: SharedString,
348 status: QuestionnaireItemStatus,
349 required: bool,
350 multiple: bool,
351 disabled: bool,
352 invalid: bool,
353 has_input: bool,
354}
355
356impl QuestionnaireItemState {
357 pub(crate) fn new(
358 name: SharedString,
359 status: QuestionnaireItemStatus,
360 required: bool,
361 multiple: bool,
362 disabled: bool,
363 invalid: bool,
364 has_input: bool,
365 ) -> Self {
366 Self {
367 name,
368 status,
369 required,
370 multiple,
371 disabled,
372 invalid,
373 has_input,
374 }
375 }
376
377 pub fn name(&self) -> &SharedString {
378 &self.name
379 }
380
381 pub fn status(&self) -> QuestionnaireItemStatus {
382 self.status
383 }
384
385 pub fn is_required(&self) -> bool {
386 self.required
387 }
388
389 pub fn is_multiple(&self) -> bool {
390 self.multiple
391 }
392
393 pub fn is_disabled(&self) -> bool {
394 self.disabled
395 }
396
397 pub fn is_invalid(&self) -> bool {
398 self.invalid
399 }
400
401 pub fn has_input(&self) -> bool {
402 self.has_input
403 }
404}
405
406#[derive(Clone, Debug, PartialEq, Eq)]
407pub struct QuestionnaireChoiceState {
408 value: SharedString,
409 selected: bool,
410 disabled: bool,
411 invalid: bool,
412 shortcut: Option<SharedString>,
413}
414
415impl QuestionnaireChoiceState {
416 pub(crate) fn new(
417 value: SharedString,
418 selected: bool,
419 disabled: bool,
420 invalid: bool,
421 shortcut: Option<SharedString>,
422 ) -> Self {
423 Self {
424 value,
425 selected,
426 disabled,
427 invalid,
428 shortcut,
429 }
430 }
431
432 pub fn value(&self) -> &SharedString {
433 &self.value
434 }
435
436 pub fn is_selected(&self) -> bool {
437 self.selected
438 }
439
440 pub fn is_disabled(&self) -> bool {
441 self.disabled
442 }
443
444 pub fn is_invalid(&self) -> bool {
445 self.invalid
446 }
447
448 pub fn shortcut(&self) -> Option<&SharedString> {
449 self.shortcut.as_ref()
450 }
451}
452
453#[derive(Clone, Debug, Default, PartialEq, Eq)]
454pub struct QuestionnaireNavigationState {
455 previous_visible: bool,
456 next_visible: bool,
457 skip_visible: bool,
458 submit_visible: bool,
459 confirmable: bool,
460}
461
462impl QuestionnaireNavigationState {
463 pub(crate) fn new(
464 previous_visible: bool,
465 next_visible: bool,
466 skip_visible: bool,
467 submit_visible: bool,
468 confirmable: bool,
469 ) -> Self {
470 Self {
471 previous_visible,
472 next_visible,
473 skip_visible,
474 submit_visible,
475 confirmable,
476 }
477 }
478
479 pub fn is_previous_visible(&self) -> bool {
480 self.previous_visible
481 }
482
483 pub fn is_next_visible(&self) -> bool {
484 self.next_visible
485 }
486
487 pub fn is_skip_visible(&self) -> bool {
488 self.skip_visible
489 }
490
491 pub fn is_submit_visible(&self) -> bool {
492 self.submit_visible
493 }
494
495 pub fn is_confirmable(&self) -> bool {
496 self.confirmable
497 }
498}
499
500#[derive(Clone, Debug)]
502pub struct QuestionnaireValidationContext {
503 item: SharedString,
504 answer: QuestionnaireAnswer,
505 answers: QuestionnaireAnswers,
506}
507
508impl QuestionnaireValidationContext {
509 pub(crate) fn new(
510 item: SharedString,
511 answer: QuestionnaireAnswer,
512 answers: QuestionnaireAnswers,
513 ) -> Self {
514 Self {
515 item,
516 answer,
517 answers,
518 }
519 }
520
521 pub fn item(&self) -> &SharedString {
522 &self.item
523 }
524
525 pub fn answer(&self) -> &QuestionnaireAnswer {
526 &self.answer
527 }
528
529 pub fn answers(&self) -> &QuestionnaireAnswers {
530 &self.answers
531 }
532}
533
534#[derive(Clone, Debug, PartialEq, Eq)]
535pub struct QuestionnaireAnswerChange {
536 item: SharedString,
537 answer: QuestionnaireAnswer,
538 status: QuestionnaireItemStatus,
539}
540
541impl QuestionnaireAnswerChange {
542 pub(crate) fn new(
543 item: SharedString,
544 answer: QuestionnaireAnswer,
545 status: QuestionnaireItemStatus,
546 ) -> Self {
547 Self {
548 item,
549 answer,
550 status,
551 }
552 }
553
554 pub fn item(&self) -> &SharedString {
555 &self.item
556 }
557
558 pub fn answer(&self) -> &QuestionnaireAnswer {
559 &self.answer
560 }
561
562 pub fn status(&self) -> QuestionnaireItemStatus {
563 self.status
564 }
565}
566
567#[derive(Clone, Debug, PartialEq, Eq)]
568pub struct QuestionnaireSubmissionItem {
569 name: SharedString,
570 status: QuestionnaireItemStatus,
571 answer: QuestionnaireAnswer,
572}
573
574impl QuestionnaireSubmissionItem {
575 pub(crate) fn new(
576 name: SharedString,
577 status: QuestionnaireItemStatus,
578 answer: QuestionnaireAnswer,
579 ) -> Self {
580 Self {
581 name,
582 status,
583 answer,
584 }
585 }
586
587 pub fn name(&self) -> &SharedString {
588 &self.name
589 }
590
591 pub fn status(&self) -> QuestionnaireItemStatus {
592 self.status
593 }
594
595 pub fn answer(&self) -> &QuestionnaireAnswer {
596 &self.answer
597 }
598}
599
600#[derive(Clone, Debug, Default, PartialEq, Eq)]
601pub struct QuestionnaireSubmission {
602 items: Vec<QuestionnaireSubmissionItem>,
603}
604
605impl QuestionnaireSubmission {
606 pub(crate) fn new(items: Vec<QuestionnaireSubmissionItem>) -> Self {
607 Self { items }
608 }
609
610 pub fn items(&self) -> &[QuestionnaireSubmissionItem] {
611 &self.items
612 }
613
614 pub fn answer(&self, name: &str) -> Option<&QuestionnaireAnswer> {
615 self.items
616 .iter()
617 .find_map(|item| (item.name.as_ref() == name).then_some(&item.answer))
618 }
619}
620
621#[derive(Clone, Debug, PartialEq, Eq)]
622#[non_exhaustive]
623pub enum QuestionnaireEvent {
624 CurrentItemChanged {
625 previous: Option<SharedString>,
626 current: Option<SharedString>,
627 },
628 AnswerChanged(QuestionnaireAnswerChange),
629 Completed(QuestionnaireSubmission),
630 Submit(QuestionnaireSubmission),
631}
632
633#[derive(Clone, Debug, PartialEq, Eq)]
639#[non_exhaustive]
640pub enum QuestionnaireValidationError {
641 Required,
643 Unanswered,
645 Message(SharedString),
647}
648
649impl QuestionnaireValidationError {
650 pub fn message(&self) -> Option<&SharedString> {
652 match self {
653 Self::Message(message) => Some(message),
654 _ => None,
655 }
656 }
657}
658
659#[derive(Clone, Debug, PartialEq, Eq)]
660#[non_exhaustive]
661pub enum QuestionnaireSchemaError {
662 DuplicateItem(SharedString),
663 DuplicateChoice {
664 item: SharedString,
665 choice: SharedString,
666 },
667 MultipleDefaultsForSingleItem(SharedString),
668 UnknownItem(SharedString),
669 UnknownChoice {
670 item: SharedString,
671 choice: SharedString,
672 },
673 AnswerDoesNotMatchItem(SharedString),
674}
675
676impl fmt::Display for QuestionnaireSchemaError {
677 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
678 match self {
679 Self::DuplicateItem(item) => write!(formatter, "duplicate questionnaire item `{item}`"),
680 Self::DuplicateChoice { item, choice } => {
681 write!(formatter, "duplicate choice `{choice}` in item `{item}`")
682 }
683 Self::MultipleDefaultsForSingleItem(item) => write!(
684 formatter,
685 "single-choice item `{item}` has more than one default answer"
686 ),
687 Self::UnknownItem(item) => write!(formatter, "unknown questionnaire item `{item}`"),
688 Self::UnknownChoice { item, choice } => {
689 write!(formatter, "unknown choice `{choice}` in item `{item}`")
690 }
691 Self::AnswerDoesNotMatchItem(item) => {
692 write!(formatter, "answer does not match item `{item}`")
693 }
694 }
695 }
696}
697
698impl Error for QuestionnaireSchemaError {}