ratatui_core/text/
masked.rs

1use alloc::borrow::Cow;
2use core::fmt;
3
4use crate::text::Text;
5
6/// A wrapper around a string that is masked when displayed.
7///
8/// The masked string is displayed as a series of the same character. This might be used to display
9/// a password field or similar secure data.
10///
11/// # Examples
12///
13/// ```rust
14/// use ratatui_core::buffer::Buffer;
15/// use ratatui_core::layout::Rect;
16/// use ratatui_core::text::{Masked, Text};
17/// use ratatui_core::widgets::Widget;
18///
19/// let mut buffer = Buffer::empty(Rect::new(0, 0, 5, 1));
20/// let password = Masked::new("12345", 'x');
21///
22/// Text::from(password).render(buffer.area, &mut buffer);
23/// assert_eq!(buffer, Buffer::with_lines(["xxxxx"]));
24/// ```
25#[derive(Default, Clone, Eq, PartialEq, Hash)]
26pub struct Masked<'a> {
27    inner: Cow<'a, str>,
28    mask_char: char,
29}
30
31impl<'a> Masked<'a> {
32    pub fn new(s: impl Into<Cow<'a, str>>, mask_char: char) -> Self {
33        Self {
34            inner: s.into(),
35            mask_char,
36        }
37    }
38
39    /// The character to use for masking.
40    pub const fn mask_char(&self) -> char {
41        self.mask_char
42    }
43
44    /// The underlying string, with all characters masked.
45    pub fn value(&self) -> Cow<'a, str> {
46        self.inner.chars().map(|_| self.mask_char).collect()
47    }
48}
49
50impl fmt::Debug for Masked<'_> {
51    /// Debug representation of a masked string is the underlying string
52    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53        // note that calling display instead of Debug here is intentional
54        fmt::Display::fmt(&self.inner, f)
55    }
56}
57
58impl fmt::Display for Masked<'_> {
59    /// Display representation of a masked string is the masked string
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        fmt::Display::fmt(&self.value(), f)
62    }
63}
64
65impl<'a> From<&'a Masked<'a>> for Cow<'a, str> {
66    fn from(masked: &'a Masked) -> Self {
67        masked.value()
68    }
69}
70
71impl<'a> From<Masked<'a>> for Cow<'a, str> {
72    fn from(masked: Masked<'a>) -> Self {
73        masked.value()
74    }
75}
76
77impl<'a> From<&'a Masked<'_>> for Text<'a> {
78    fn from(masked: &'a Masked) -> Self {
79        Text::raw(masked.value())
80    }
81}
82
83impl<'a> From<Masked<'a>> for Text<'a> {
84    fn from(masked: Masked<'a>) -> Self {
85        Text::raw(masked.value())
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use alloc::format;
92
93    use super::*;
94    use crate::text::Line;
95
96    #[test]
97    fn new() {
98        let masked = Masked::new("12345", 'x');
99        assert_eq!(masked.inner, "12345");
100        assert_eq!(masked.mask_char, 'x');
101    }
102
103    #[test]
104    fn value() {
105        let masked = Masked::new("12345", 'x');
106        assert_eq!(masked.value(), "xxxxx");
107    }
108
109    #[test]
110    fn mask_char() {
111        let masked = Masked::new("12345", 'x');
112        assert_eq!(masked.mask_char(), 'x');
113    }
114
115    #[test]
116    fn debug() {
117        let masked = Masked::new("12345", 'x');
118        assert_eq!(format!("{masked:?}"), "12345");
119        assert_eq!(format!("{masked:.3?}"), "123", "Debug truncates");
120    }
121
122    #[test]
123    fn display() {
124        let masked = Masked::new("12345", 'x');
125        assert_eq!(format!("{masked}"), "xxxxx");
126        assert_eq!(format!("{masked:.3}"), "xxx", "Display truncates");
127    }
128
129    #[test]
130    fn into_text() {
131        let masked = Masked::new("12345", 'x');
132
133        let text: Text = (&masked).into();
134        assert_eq!(text.lines, [Line::from("xxxxx")]);
135
136        let text: Text = masked.into();
137        assert_eq!(text.lines, [Line::from("xxxxx")]);
138    }
139
140    #[test]
141    fn into_cow() {
142        let masked = Masked::new("12345", 'x');
143        let cow: Cow<str> = (&masked).into();
144        assert_eq!(cow, "xxxxx");
145
146        let cow: Cow<str> = masked.into();
147        assert_eq!(cow, "xxxxx");
148    }
149}