1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use std::{borrow::Cow, fmt};

use super::Text;

/// A wrapper around a string that is masked when displayed.
///
/// The masked string is displayed as a series of the same character.
/// This might be used to display a password field or similar secure data.
///
/// # Examples
///
/// ```rust
/// use ratatui::{prelude::*, widgets::*};
///
/// let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 1));
/// let password = Masked::new("12345", 'x');
///
/// Paragraph::new(password).render(buffer.area, &mut buffer);
/// assert_eq!(buffer, Buffer::with_lines(["xxxxx"]));
/// ```
#[derive(Default, Clone, Eq, PartialEq, Hash)]
pub struct Masked<'a> {
    inner: Cow<'a, str>,
    mask_char: char,
}

impl<'a> Masked<'a> {
    pub fn new(s: impl Into<Cow<'a, str>>, mask_char: char) -> Self {
        Self {
            inner: s.into(),
            mask_char,
        }
    }

    /// The character to use for masking.
    pub const fn mask_char(&self) -> char {
        self.mask_char
    }

    /// The underlying string, with all characters masked.
    pub fn value(&self) -> Cow<'a, str> {
        self.inner.chars().map(|_| self.mask_char).collect()
    }
}

impl fmt::Debug for Masked<'_> {
    /// Debug representation of a masked string is the underlying string
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // note that calling display instead of Debug here is intentional
        fmt::Display::fmt(&self.inner, f)
    }
}

impl fmt::Display for Masked<'_> {
    /// Display representation of a masked string is the masked string
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.value(), f)
    }
}

impl<'a> From<&'a Masked<'a>> for Cow<'a, str> {
    fn from(masked: &'a Masked) -> Self {
        masked.value()
    }
}

impl<'a> From<Masked<'a>> for Cow<'a, str> {
    fn from(masked: Masked<'a>) -> Self {
        masked.value()
    }
}

impl<'a> From<&'a Masked<'_>> for Text<'a> {
    fn from(masked: &'a Masked) -> Self {
        Text::raw(masked.value())
    }
}

impl<'a> From<Masked<'a>> for Text<'a> {
    fn from(masked: Masked<'a>) -> Self {
        Text::raw(masked.value())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::text::Line;

    #[test]
    fn new() {
        let masked = Masked::new("12345", 'x');
        assert_eq!(masked.inner, "12345");
        assert_eq!(masked.mask_char, 'x');
    }

    #[test]
    fn value() {
        let masked = Masked::new("12345", 'x');
        assert_eq!(masked.value(), "xxxxx");
    }

    #[test]
    fn mask_char() {
        let masked = Masked::new("12345", 'x');
        assert_eq!(masked.mask_char(), 'x');
    }

    #[test]
    fn debug() {
        let masked = Masked::new("12345", 'x');
        assert_eq!(format!("{masked:?}"), "12345");
        assert_eq!(format!("{masked:.3?}"), "123", "Debug truncates");
    }

    #[test]
    fn display() {
        let masked = Masked::new("12345", 'x');
        assert_eq!(format!("{masked}"), "xxxxx");
        assert_eq!(format!("{masked:.3}"), "xxx", "Display truncates");
    }

    #[test]
    fn into_text() {
        let masked = Masked::new("12345", 'x');

        let text: Text = (&masked).into();
        assert_eq!(text.lines, vec![Line::from("xxxxx")]);

        let text: Text = masked.into();
        assert_eq!(text.lines, vec![Line::from("xxxxx")]);
    }

    #[test]
    fn into_cow() {
        let masked = Masked::new("12345", 'x');
        let cow: Cow<str> = (&masked).into();
        assert_eq!(cow, "xxxxx");

        let cow: Cow<str> = masked.into();
        assert_eq!(cow, "xxxxx");
    }
}