inquire/prompts/password/
prompt.rs

1use crate::{
2    error::InquireResult,
3    formatter::StringFormatter,
4    input::Input,
5    prompts::prompt::{ActionResult, Prompt},
6    ui::PasswordBackend,
7    validator::{ErrorMessage, StringValidator, Validation},
8    InquireError, Password, PasswordDisplayMode,
9};
10
11use super::{action::PasswordPromptAction, config::PasswordConfig};
12
13// Helper type for representing the password confirmation flow.
14struct PasswordConfirmation<'a> {
15    // The message of the prompt.
16    pub message: &'a str,
17
18    // The error message of the prompt.
19    pub error_message: &'a str,
20
21    // The input to confirm.
22    pub input: Input,
23}
24
25pub struct PasswordPrompt<'a> {
26    message: &'a str,
27    config: PasswordConfig,
28    help_message: Option<&'a str>,
29    input: Input,
30    current_mode: PasswordDisplayMode,
31    confirmation: Option<PasswordConfirmation<'a>>, // if `None`, confirmation is disabled, `Some(_)` confirmation is enabled
32    confirmation_stage: bool,
33    formatter: StringFormatter<'a>,
34    validators: Vec<Box<dyn StringValidator>>,
35    error: Option<ErrorMessage>,
36}
37
38impl<'a> From<Password<'a>> for PasswordPrompt<'a> {
39    fn from(so: Password<'a>) -> Self {
40        let confirmation = match so.enable_confirmation {
41            true => Some(PasswordConfirmation {
42                message: so.custom_confirmation_message.unwrap_or("Confirmation:"),
43                error_message: so
44                    .custom_confirmation_error_message
45                    .unwrap_or("The answers don't match."),
46                input: Input::new(),
47            }),
48            false => None,
49        };
50
51        Self {
52            message: so.message,
53            config: (&so).into(),
54            help_message: so.help_message,
55            current_mode: so.display_mode,
56            confirmation,
57            confirmation_stage: false,
58            formatter: so.formatter,
59            validators: so.validators,
60            input: Input::new(),
61            error: None,
62        }
63    }
64}
65
66impl<'a> From<&'a str> for Password<'a> {
67    fn from(val: &'a str) -> Self {
68        Password::new(val)
69    }
70}
71
72impl<'a> PasswordPrompt<'a> {
73    fn active_input_mut(&mut self) -> &mut Input {
74        if let Some(c) = &mut self.confirmation {
75            if self.confirmation_stage {
76                return &mut c.input;
77            }
78        }
79
80        &mut self.input
81    }
82
83    fn toggle_display_mode(&mut self) -> ActionResult {
84        let new_mode = match self.current_mode {
85            PasswordDisplayMode::Hidden | PasswordDisplayMode::Masked => PasswordDisplayMode::Full,
86            PasswordDisplayMode::Full => self.config.display_mode,
87        };
88
89        if new_mode != self.current_mode {
90            self.current_mode = new_mode;
91            ActionResult::NeedsRedraw
92        } else {
93            ActionResult::Clean
94        }
95    }
96
97    fn confirmation_step(&mut self) -> ConfirmationStepResult {
98        let cur_answer = self.cur_answer().to_owned();
99        match &mut self.confirmation {
100            None => ConfirmationStepResult::NoConfirmationRequired,
101            Some(confirmation) => {
102                if self.confirmation_stage {
103                    if cur_answer == confirmation.input.content() {
104                        ConfirmationStepResult::ConfirmationValidated
105                    } else {
106                        self.confirmation_stage = false;
107                        confirmation.input.clear();
108                        ConfirmationStepResult::ConfirmationInvalidated(ErrorMessage::Custom(
109                            confirmation.error_message.to_owned(),
110                        ))
111                    }
112                } else {
113                    confirmation.input.clear();
114                    self.confirmation_stage = true;
115
116                    ConfirmationStepResult::ConfirmationPending
117                }
118            }
119        }
120    }
121
122    fn validate_current_answer(&self) -> InquireResult<Validation> {
123        for validator in &self.validators {
124            match validator.validate(self.cur_answer()) {
125                Ok(Validation::Valid) => {}
126                Ok(Validation::Invalid(msg)) => return Ok(Validation::Invalid(msg)),
127                Err(err) => return Err(InquireError::Custom(err)),
128            }
129        }
130
131        Ok(Validation::Valid)
132    }
133
134    fn cur_answer(&self) -> &str {
135        self.input.content()
136    }
137}
138
139impl<'a, Backend> Prompt<Backend> for PasswordPrompt<'a>
140where
141    Backend: PasswordBackend,
142{
143    type Config = PasswordConfig;
144    type InnerAction = PasswordPromptAction;
145    type Output = String;
146
147    fn message(&self) -> &str {
148        self.message
149    }
150
151    fn config(&self) -> &PasswordConfig {
152        &self.config
153    }
154
155    fn format_answer(&self, answer: &String) -> String {
156        (self.formatter)(answer)
157    }
158
159    fn pre_cancel(&mut self) -> InquireResult<bool> {
160        if let Some(confirmation) = &mut self.confirmation {
161            if self.confirmation_stage {
162                confirmation.input.clear();
163                self.confirmation_stage = false;
164                return Ok(false);
165            }
166        }
167
168        Ok(true)
169    }
170
171    fn submit(&mut self) -> InquireResult<Option<String>> {
172        if let Validation::Invalid(msg) = self.validate_current_answer()? {
173            self.error = Some(msg);
174            if self.config.display_mode == PasswordDisplayMode::Hidden {
175                self.input.clear();
176            }
177            return Ok(None);
178        }
179
180        let confirmation = self.confirmation_step();
181
182        let cur_answer = self.cur_answer().to_owned();
183
184        let result = match confirmation {
185            ConfirmationStepResult::NoConfirmationRequired
186            | ConfirmationStepResult::ConfirmationValidated => Some(cur_answer),
187            ConfirmationStepResult::ConfirmationPending => None,
188            ConfirmationStepResult::ConfirmationInvalidated(message) => {
189                self.error = Some(message);
190                self.input.clear();
191                None
192            }
193        };
194
195        Ok(result)
196    }
197
198    fn handle(&mut self, action: PasswordPromptAction) -> InquireResult<ActionResult> {
199        let result = match action {
200            PasswordPromptAction::ValueInput(input_action) => {
201                self.active_input_mut().handle(input_action).into()
202            }
203            PasswordPromptAction::ToggleDisplayMode => self.toggle_display_mode(),
204        };
205
206        Ok(result)
207    }
208
209    fn render(&self, backend: &mut Backend) -> InquireResult<()> {
210        if let Some(err) = &self.error {
211            backend.render_error_message(err)?;
212        }
213
214        match self.current_mode {
215            PasswordDisplayMode::Hidden => {
216                backend.render_prompt(self.message)?;
217
218                match &self.confirmation {
219                    Some(confirmation) if self.confirmation_stage => {
220                        backend.render_prompt(confirmation.message)?;
221                    }
222                    _ => {}
223                }
224            }
225            PasswordDisplayMode::Masked => {
226                backend.render_prompt_with_masked_input(self.message, &self.input)?;
227
228                match &self.confirmation {
229                    Some(confirmation) if self.confirmation_stage => {
230                        backend.render_prompt_with_masked_input(
231                            confirmation.message,
232                            &confirmation.input,
233                        )?;
234                    }
235                    _ => {}
236                }
237            }
238            PasswordDisplayMode::Full => {
239                backend.render_prompt_with_full_input(self.message, &self.input)?;
240
241                match &self.confirmation {
242                    Some(confirmation) if self.confirmation_stage => {
243                        backend.render_prompt_with_full_input(
244                            confirmation.message,
245                            &confirmation.input,
246                        )?;
247                    }
248                    _ => {}
249                }
250            }
251        }
252
253        if let Some(message) = self.help_message {
254            backend.render_help_message(message)?;
255        }
256
257        Ok(())
258    }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq)]
262pub enum ConfirmationStepResult {
263    NoConfirmationRequired,
264    ConfirmationPending,
265    ConfirmationValidated,
266    ConfirmationInvalidated(ErrorMessage),
267}