Skip to main content

standout_input/questionnaire/
derive.rs

1use super::{
2    Answers, FormError, Item, Questionnaire, QuestionnaireError, RawAnswers, ValidationDiagnostic,
3};
4
5pub trait QuestionnaireChoices:
6    Sized + std::str::FromStr<Err = QuestionnaireChoiceParseError> + std::fmt::Display
7{
8    fn choices() -> &'static [&'static str];
9}
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct QuestionnaireChoiceParseError {
13    choices: &'static [&'static str],
14}
15
16impl QuestionnaireChoiceParseError {
17    pub const fn new(choices: &'static [&'static str]) -> Self {
18        Self { choices }
19    }
20
21    pub fn choices(&self) -> &'static [&'static str] {
22        self.choices
23    }
24}
25
26impl std::fmt::Display for QuestionnaireChoiceParseError {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        write!(f, "expected one of: {}", self.choices.join(", "))
29    }
30}
31
32impl std::error::Error for QuestionnaireChoiceParseError {}
33
34pub trait QuestionnaireInput: Sized {
35    fn questionnaire() -> Result<Questionnaire, QuestionnaireError>;
36
37    #[doc(hidden)]
38    fn from_decoded_answers(answers: &Answers) -> Self;
39
40    #[doc(hidden)]
41    fn questionnaire_items(prefix: &str) -> Vec<Item> {
42        assert!(
43            prefix.is_empty(),
44            "manual QuestionnaireInput implementation cannot be nested unless questionnaire_items is overridden"
45        );
46        Self::questionnaire()
47            .expect("manual QuestionnaireInput implementation cannot be nested unless questionnaire_items is overridden")
48            .items()
49            .to_vec()
50    }
51
52    #[doc(hidden)]
53    fn from_decoded_answers_at(answers: &Answers, prefix: &str) -> Self {
54        assert!(
55            prefix.is_empty(),
56            "manual QuestionnaireInput implementation cannot be nested unless from_decoded_answers_at is overridden"
57        );
58        Self::from_decoded_answers(answers)
59    }
60
61    fn from_raw_answers(raw: &RawAnswers) -> Result<Self, QuestionnaireInputError> {
62        let questionnaire = Self::questionnaire().map_err(QuestionnaireInputError::Definition)?;
63        let answers = questionnaire
64            .decode_answers(raw)
65            .map_err(QuestionnaireInputError::Validation)?;
66        Ok(Self::from_decoded_answers(&answers))
67    }
68
69    fn from_raw_answers_with<F>(raw: &RawAnswers, form: F) -> Result<Self, QuestionnaireInputError>
70    where
71        F: FnOnce(&Self) -> Vec<FormError>,
72    {
73        let questionnaire = Self::questionnaire().map_err(QuestionnaireInputError::Definition)?;
74        let answers = questionnaire
75            .decode_answers(raw)
76            .map_err(QuestionnaireInputError::Validation)?;
77        let value = Self::from_decoded_answers(&answers);
78        let form_errors = form(&value);
79        if form_errors.is_empty() {
80            return Ok(value);
81        }
82        Err(QuestionnaireInputError::Validation(
83            form_errors
84                .into_iter()
85                .map(|error| ValidationDiagnostic::Form {
86                    fields: error.fields,
87                    message: error.message,
88                })
89                .collect(),
90        ))
91    }
92}
93
94#[derive(Debug, thiserror::Error, PartialEq, Eq)]
95pub enum QuestionnaireInputError {
96    #[error("derived questionnaire definition is invalid: {0}")]
97    Definition(QuestionnaireError),
98
99    #[error("derived questionnaire answers are invalid: {}", validation_diagnostics_display(.0))]
100    Validation(Vec<ValidationDiagnostic>),
101}
102
103fn validation_diagnostics_display(diagnostics: &[ValidationDiagnostic]) -> String {
104    if diagnostics.is_empty() {
105        return "no diagnostics reported".to_string();
106    }
107
108    diagnostics
109        .iter()
110        .map(ToString::to_string)
111        .collect::<Vec<_>>()
112        .join("; ")
113}