1use crate::{
2 config::get_configuration,
3 error::{InquireError, InquireResult},
4 formatter::StringFormatter,
5 input::Input,
6 terminal::get_default_terminal,
7 ui::{Backend, Key, KeyModifiers, PasswordBackend, RenderConfig},
8 validator::{ErrorMessage, StringValidator, Validation},
9};
10
11#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13pub enum PasswordDisplayMode {
14 Hidden,
16
17 Masked,
21
22 Full,
25}
26
27struct PasswordConfirmation<'a> {
29 message: &'a str,
31
32 error_message: &'a str,
34
35 input: Input,
37}
38
39#[derive(Clone)]
89pub struct Password<'a> {
90 pub message: &'a str,
92
93 pub custom_confirmation_message: Option<&'a str>,
95
96 pub custom_confirmation_error_message: Option<&'a str>,
98
99 pub help_message: Option<&'a str>,
101
102 pub formatter: StringFormatter<'a>,
104
105 pub display_mode: PasswordDisplayMode,
107
108 pub enable_display_toggle: bool,
110
111 pub enable_confirmation: bool,
113
114 pub validators: Vec<Box<dyn StringValidator>>,
121
122 pub render_config: RenderConfig,
131}
132
133impl<'a> Password<'a> {
134 pub const DEFAULT_FORMATTER: StringFormatter<'a> = &|_| String::from("********");
136
137 pub const DEFAULT_VALIDATORS: Vec<Box<dyn StringValidator>> = vec![];
139
140 pub const DEFAULT_HELP_MESSAGE: Option<&'a str> = None;
142
143 pub const DEFAULT_ENABLE_DISPLAY_TOGGLE: bool = false;
145
146 pub const DEFAULT_ENABLE_CONFIRMATION: bool = true;
148
149 pub const DEFAULT_DISPLAY_MODE: PasswordDisplayMode = PasswordDisplayMode::Hidden;
151
152 pub fn new(message: &'a str) -> Self {
154 Self {
155 message,
156 custom_confirmation_message: None,
157 custom_confirmation_error_message: None,
158 enable_confirmation: Self::DEFAULT_ENABLE_CONFIRMATION,
159 enable_display_toggle: Self::DEFAULT_ENABLE_DISPLAY_TOGGLE,
160 display_mode: Self::DEFAULT_DISPLAY_MODE,
161 help_message: Self::DEFAULT_HELP_MESSAGE,
162 formatter: Self::DEFAULT_FORMATTER,
163 validators: Self::DEFAULT_VALIDATORS,
164 render_config: get_configuration(),
165 }
166 }
167
168 pub fn with_help_message(mut self, message: &'a str) -> Self {
170 self.help_message = Some(message);
171 self
172 }
173
174 pub fn with_display_toggle_enabled(mut self) -> Self {
176 self.enable_display_toggle = true;
177 self
178 }
179
180 pub fn without_confirmation(mut self) -> Self {
182 self.enable_confirmation = false;
183 self
184 }
185
186 pub fn with_custom_confirmation_message(mut self, message: &'a str) -> Self {
188 self.custom_confirmation_message.replace(message);
189 self
190 }
191
192 pub fn with_custom_confirmation_error_message(mut self, message: &'a str) -> Self {
194 self.custom_confirmation_error_message.replace(message);
195 self
196 }
197
198 pub fn with_display_mode(mut self, mode: PasswordDisplayMode) -> Self {
200 self.display_mode = mode;
201 self
202 }
203
204 pub fn with_formatter(mut self, formatter: StringFormatter<'a>) -> Self {
206 self.formatter = formatter;
207 self
208 }
209
210 pub fn with_validator<V>(mut self, validator: V) -> Self
219 where
220 V: StringValidator + 'static,
221 {
222 if self.validators.capacity() == 0 {
225 self.validators.reserve(5);
226 }
227
228 self.validators.push(Box::new(validator));
229 self
230 }
231
232 pub fn with_validators(mut self, validators: &[Box<dyn StringValidator>]) -> Self {
239 for validator in validators {
240 #[allow(clippy::clone_double_ref)]
241 self.validators.push(validator.clone());
242 }
243 self
244 }
245
246 pub fn with_render_config(mut self, render_config: RenderConfig) -> Self {
255 self.render_config = render_config;
256 self
257 }
258
259 pub fn prompt_skippable(self) -> InquireResult<Option<String>> {
269 match self.prompt() {
270 Ok(answer) => Ok(Some(answer)),
271 Err(InquireError::OperationCanceled) => Ok(None),
272 Err(err) => Err(err),
273 }
274 }
275
276 pub fn prompt(self) -> InquireResult<String> {
279 let terminal = get_default_terminal()?;
280 let mut backend = Backend::new(terminal, self.render_config)?;
281 self.prompt_with_backend(&mut backend)
282 }
283
284 pub(crate) fn prompt_with_backend<B: PasswordBackend>(
285 self,
286 backend: &mut B,
287 ) -> InquireResult<String> {
288 PasswordPrompt::from(self).prompt(backend)
289 }
290}
291
292struct PasswordPrompt<'a> {
293 message: &'a str,
294 help_message: Option<&'a str>,
295 input: Input,
296 standard_display_mode: PasswordDisplayMode,
297 display_mode: PasswordDisplayMode,
298 enable_display_toggle: bool,
299 confirmation: Option<PasswordConfirmation<'a>>, confirmation_stage: bool,
301 formatter: StringFormatter<'a>,
302 validators: Vec<Box<dyn StringValidator>>,
303 error: Option<ErrorMessage>,
304}
305
306impl<'a> From<Password<'a>> for PasswordPrompt<'a> {
307 fn from(so: Password<'a>) -> Self {
308 let confirmation = match so.enable_confirmation {
309 true => Some(PasswordConfirmation {
310 message: so.custom_confirmation_message.unwrap_or("Confirmation:"),
311 error_message: so
312 .custom_confirmation_error_message
313 .unwrap_or("The answers don't match."),
314 input: Input::new(),
315 }),
316 false => None,
317 };
318
319 Self {
320 message: so.message,
321 help_message: so.help_message,
322 standard_display_mode: so.display_mode,
323 display_mode: so.display_mode,
324 enable_display_toggle: so.enable_display_toggle,
325 confirmation,
326 confirmation_stage: false,
327 formatter: so.formatter,
328 validators: so.validators,
329 input: Input::new(),
330 error: None,
331 }
332 }
333}
334
335impl<'a> From<&'a str> for Password<'a> {
336 fn from(val: &'a str) -> Self {
337 Password::new(val)
338 }
339}
340
341impl<'a> PasswordPrompt<'a> {
342 fn active_input(&self) -> &Input {
343 match &self.confirmation {
344 Some(confirmation) if self.confirmation_stage => &confirmation.input,
345 _ => &self.input,
346 }
347 }
348
349 fn active_input_mut(&mut self) -> &mut Input {
350 match &mut self.confirmation {
351 Some(confirmation) if self.confirmation_stage => &mut confirmation.input,
352 _ => &mut self.input,
353 }
354 }
355
356 fn on_change(&mut self, key: Key) {
357 match key {
358 Key::Char('r', m) | Key::Char('R', m)
359 if m.contains(KeyModifiers::CONTROL) && self.enable_display_toggle =>
360 {
361 self.toggle_display_mode();
362 }
363 _ => {
364 self.active_input_mut().handle_key(key);
365 }
366 };
367 }
368
369 fn toggle_display_mode(&mut self) {
370 self.display_mode = match self.display_mode {
371 PasswordDisplayMode::Hidden => PasswordDisplayMode::Full,
372 PasswordDisplayMode::Masked => PasswordDisplayMode::Full,
373 PasswordDisplayMode::Full => self.standard_display_mode,
374 }
375 }
376
377 fn handle_cancel(&mut self) -> bool {
378 if self.confirmation_stage && self.confirmation.is_some() {
379 if self.display_mode == PasswordDisplayMode::Hidden {
380 self.input.clear();
381 }
382
383 self.error = None;
384 self.confirmation_stage = false;
385
386 true
387 } else {
388 false
389 }
390 }
391
392 fn handle_submit(&mut self) -> InquireResult<Option<String>> {
393 let answer = match self.validate_current_answer()? {
394 Validation::Valid => self.confirm_current_answer(),
395 Validation::Invalid(msg) => {
396 self.error = Some(msg);
397 None
398 }
399 };
400
401 Ok(answer)
402 }
403
404 fn confirm_current_answer(&mut self) -> Option<String> {
405 let cur_answer = self.cur_answer();
406 match &mut self.confirmation {
407 None => Some(cur_answer),
408 Some(confirmation) => {
409 if !self.confirmation_stage {
410 if self.display_mode == PasswordDisplayMode::Hidden {
411 confirmation.input.clear();
412 }
413
414 self.error = None;
415 self.confirmation_stage = true;
416
417 None
418 } else if self.input.content() == cur_answer {
419 Some(confirmation.input.content().into())
420 } else {
421 confirmation.input.clear();
422
423 self.error = Some(confirmation.error_message.into());
424 self.confirmation_stage = false;
425
426 None
427 }
428 }
429 }
430 }
431
432 fn validate_current_answer(&self) -> InquireResult<Validation> {
433 for validator in &self.validators {
434 match validator.validate(self.active_input().content()) {
435 Ok(Validation::Valid) => {}
436 Ok(Validation::Invalid(msg)) => return Ok(Validation::Invalid(msg)),
437 Err(err) => return Err(InquireError::Custom(err)),
438 }
439 }
440
441 Ok(Validation::Valid)
442 }
443
444 fn cur_answer(&self) -> String {
445 self.active_input().content().into()
446 }
447
448 fn render<B: PasswordBackend>(&mut self, backend: &mut B) -> InquireResult<()> {
449 backend.frame_setup()?;
450
451 if let Some(err) = &self.error {
452 backend.render_error_message(err)?;
453 }
454
455 match self.display_mode {
456 PasswordDisplayMode::Hidden => {
457 backend.render_prompt(self.message)?;
458
459 match &self.confirmation {
460 Some(confirmation) if self.confirmation_stage => {
461 backend.render_prompt(confirmation.message)?
462 }
463 _ => {}
464 }
465 }
466 PasswordDisplayMode::Masked => {
467 backend.render_prompt_with_masked_input(self.message, &self.input)?;
468
469 match &self.confirmation {
470 Some(confirmation) if self.confirmation_stage => {
471 backend.render_prompt_with_masked_input(
472 confirmation.message,
473 &confirmation.input,
474 )?;
475 }
476 _ => {}
477 }
478 }
479 PasswordDisplayMode::Full => {
480 backend.render_prompt_with_full_input(self.message, &self.input)?;
481
482 match &self.confirmation {
483 Some(confirmation) if self.confirmation_stage => {
484 backend.render_prompt_with_full_input(
485 confirmation.message,
486 &confirmation.input,
487 )?;
488 }
489 _ => {}
490 }
491 }
492 }
493
494 if let Some(message) = self.help_message {
495 backend.render_help_message(message)?;
496 }
497
498 backend.frame_finish()?;
499
500 Ok(())
501 }
502
503 fn prompt<B: PasswordBackend>(mut self, backend: &mut B) -> InquireResult<String> {
504 let final_answer = loop {
505 self.render(backend)?;
506
507 let key = backend.read_key()?;
508
509 match key {
510 Key::Interrupt => interrupt_prompt!(),
511 Key::Cancel => {
512 if !self.handle_cancel() {
513 cancel_prompt!(backend, self.message);
514 }
515 }
516 Key::Submit => {
517 if let Some(answer) = self.handle_submit()? {
518 break answer;
519 }
520 }
521 key => self.on_change(key),
522 }
523 };
524
525 let formatted = (self.formatter)(&final_answer);
526
527 finish_prompt_with_answer!(backend, self.message, &formatted, final_answer);
528 }
529}
530
531#[cfg(test)]
532#[cfg(feature = "crossterm")]
533mod test {
534 use super::Password;
535 use crate::{
536 terminal::crossterm::CrosstermTerminal,
537 ui::{Backend, RenderConfig},
538 validator::{ErrorMessage, Validation},
539 };
540 use crossterm::event::{KeyCode, KeyEvent};
541
542 macro_rules! text_to_events {
543 ($text:expr) => {{
544 $text.chars().map(KeyCode::Char)
545 }};
546 }
547
548 macro_rules! password_test {
549 ($(#[$meta:meta])? $name:ident,$input:expr,$output:expr,$prompt:expr) => {
550 #[test]
551 $(#[$meta])?
552 fn $name() {
553 let read: Vec<KeyEvent> = $input.into_iter().map(KeyEvent::from).collect();
554 let mut read = read.iter();
555
556 let mut write: Vec<u8> = Vec::new();
557 let terminal = CrosstermTerminal::new_with_io(&mut write, &mut read);
558 let mut backend = Backend::new(terminal, RenderConfig::default()).unwrap();
559
560 let ans = $prompt.prompt_with_backend(&mut backend).unwrap();
561
562 assert_eq!($output, ans);
563 }
564 };
565 }
566
567 password_test!(
568 empty,
569 vec![KeyCode::Enter],
570 "",
571 Password::new("").without_confirmation()
572 );
573
574 password_test!(
575 single_letter,
576 vec![KeyCode::Char('b'), KeyCode::Enter],
577 "b",
578 Password::new("").without_confirmation()
579 );
580
581 password_test!(
582 letters_and_enter,
583 text_to_events!("normal input\n"),
584 "normal input",
585 Password::new("").without_confirmation()
586 );
587
588 password_test!(
589 letters_and_enter_with_emoji,
590 text_to_events!("with emoji 🧘🏻♂️, 🌍, 🍞, 🚗, 📞\n"),
591 "with emoji 🧘🏻♂️, 🌍, 🍞, 🚗, 📞",
592 Password::new("").without_confirmation()
593 );
594
595 password_test!(
596 input_and_correction,
597 {
598 let mut events = vec![];
599 events.append(&mut text_to_events!("anor").collect());
600 events.push(KeyCode::Backspace);
601 events.push(KeyCode::Backspace);
602 events.push(KeyCode::Backspace);
603 events.push(KeyCode::Backspace);
604 events.append(&mut text_to_events!("normal input").collect());
605 events.push(KeyCode::Enter);
606 events
607 },
608 "normal input",
609 Password::new("").without_confirmation()
610 );
611
612 password_test!(
613 input_and_excessive_correction,
614 {
615 let mut events = vec![];
616 events.append(&mut text_to_events!("anor").collect());
617 events.push(KeyCode::Backspace);
618 events.push(KeyCode::Backspace);
619 events.push(KeyCode::Backspace);
620 events.push(KeyCode::Backspace);
621 events.push(KeyCode::Backspace);
622 events.push(KeyCode::Backspace);
623 events.push(KeyCode::Backspace);
624 events.push(KeyCode::Backspace);
625 events.push(KeyCode::Backspace);
626 events.push(KeyCode::Backspace);
627 events.append(&mut text_to_events!("normal input").collect());
628 events.push(KeyCode::Enter);
629 events
630 },
631 "normal input",
632 Password::new("").without_confirmation()
633 );
634
635 password_test!(
636 input_correction_after_validation,
637 {
638 let mut events = vec![];
639 events.append(&mut text_to_events!("1234567890").collect());
640 events.push(KeyCode::Enter);
641 events.push(KeyCode::Backspace);
642 events.push(KeyCode::Backspace);
643 events.push(KeyCode::Backspace);
644 events.push(KeyCode::Backspace);
645 events.push(KeyCode::Backspace);
646 events.append(&mut text_to_events!("yes").collect());
647 events.push(KeyCode::Enter);
648 events
649 },
650 "12345yes",
651 Password::new("")
652 .without_confirmation()
653 .with_validator(|ans: &str| match ans.len() {
654 len if len > 5 && len < 10 => Ok(Validation::Valid),
655 _ => Ok(Validation::Invalid(ErrorMessage::Default)),
656 })
657 );
658
659 password_test!(
660 input_confirmation_same,
661 {
662 let mut events = vec![];
663 events.append(&mut text_to_events!("1234567890").collect());
664 events.push(KeyCode::Enter);
665 events.append(&mut text_to_events!("1234567890").collect());
666 events.push(KeyCode::Enter);
667 events
668 },
669 "1234567890",
670 Password::new("")
671 );
672
673 password_test!(
674 #[should_panic(expected = "Custom stream of characters has ended")]
675 input_confirmation_different,
676 {
677 let mut events = vec![];
678 events.append(&mut text_to_events!("1234567890").collect());
679 events.push(KeyCode::Enter);
680 events.append(&mut text_to_events!("abcdefghij").collect());
681 events.push(KeyCode::Enter);
682 events
683 },
684 "",
685 Password::new("")
686 );
687}