secret_string/
lib.rs

1//! A wrapper around strings that hides their contents when printed or
2//! formatted for debugging.///
3//! It WILL however serialize the value when using [serde](https://serde.rs) serialization/deserialization.
4//!
5//! # Examples
6//! ```
7//! use secret_string::SecretString;
8//! let secret = SecretString::new("my_secret_password");
9//! assert_eq!(format!("{}", secret), "******************");
10//! assert_eq!(format!("{:?}", secret), "SecretString(******************)");
11//! assert_eq!(secret.value(), "my_secret_password");
12//! ```
13
14#![deny(warnings)]
15#![warn(unused_extern_crates)]
16#![deny(clippy::todo)]
17#![deny(clippy::unimplemented)]
18#![deny(clippy::unwrap_used)]
19#![deny(clippy::expect_used)]
20#![deny(clippy::panic)]
21#![deny(clippy::unreachable)]
22#![deny(clippy::await_holding_lock)]
23#![deny(clippy::needless_pass_by_value)]
24#![deny(clippy::trivially_copy_pass_by_ref)]
25
26use std::fmt::Debug;
27
28/// A string wrapper that hides its contents when printed or formatted for
29/// debugging.
30///
31/// It WILL however serialize the value when using [serde](https://serde.rs) serialization/deserialization.
32///
33/// # Examples
34/// ```
35/// use secret_string::SecretString;
36/// let secret = SecretString::new("my_secret_password");
37/// assert_eq!(format!("{}", secret), "******************");
38/// assert_eq!(format!("{:?}", secret), "SecretString(******************)");
39/// assert_eq!(secret.value(), "my_secret_password");
40/// ```
41
42#[derive(Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
43pub struct SecretString<T>(T)
44where
45    T: AsRef<str>;
46
47impl<T: AsRef<str>> SecretString<T> {
48    pub fn new(s: T) -> Self {
49        SecretString(s)
50    }
51
52    /// Returns the underlying value.
53    pub fn value(&self) -> &str {
54        self.0.as_ref()
55    }
56
57    pub fn len(&self) -> usize {
58        self.0.as_ref().len()
59    }
60
61    pub fn is_empty(&self) -> bool {
62        self.len() == 0
63    }
64
65    /// Returns a string of asterisks (*) with the same length as the secret string.
66    pub fn as_stars(&self) -> String {
67        String::from("*").repeat(self.len())
68    }
69
70    /// In case you don't want to show the length of the secret
71    pub fn as_stars_with_with_len(&self, len: usize) -> String {
72        String::from("*").repeat(len)
73    }
74}
75
76impl<T: AsRef<str>> From<T> for SecretString<T> {
77    fn from(s: T) -> Self {
78        SecretString(s)
79    }
80}
81
82impl<T: AsRef<str>> std::fmt::Display for SecretString<T> {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        write!(f, "{}", self.as_stars())
85    }
86}
87
88impl<T: AsRef<str>> Debug for SecretString<T> {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        write!(f, "SecretString({})", self.as_stars())
91    }
92}
93
94#[cfg(feature = "serde")]
95impl<T: AsRef<str> + serde::Serialize> serde::Serialize for SecretString<T> {
96    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
97    where
98        S: serde::Serializer,
99    {
100        serializer.serialize_str(self.value())
101    }
102}
103
104#[cfg(feature = "serde")]
105impl<'de, T: AsRef<str> + serde::Deserialize<'de>> serde::Deserialize<'de> for SecretString<T> {
106    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
107    where
108        D: serde::Deserializer<'de>,
109    {
110        Ok(Self::new(serde::Deserialize::deserialize(deserializer)?))
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::SecretString;
117
118    #[test]
119    fn test_secret_string_display() {
120        let secret = SecretString::new("my_secret_password");
121        assert_eq!(format!("{}", secret), "******************");
122    }
123
124    #[test]
125    fn test_secret_string_debug() {
126        let secret = SecretString::new("my_secret_password");
127        assert_eq!(format!("{:?}", secret), "SecretString(******************)");
128    }
129    #[test]
130    fn test_secret_string_value() {
131        let secret = SecretString::new("my_secret_password");
132        assert_eq!(secret.value(), "my_secret_password");
133    }
134
135    #[cfg(feature = "serde")]
136    #[test]
137    fn test_secret_string_serde() {
138        let secret = SecretString::new("my_secret_password");
139        let serialized = serde_json::to_string(&secret).expect("Failed to serialize");
140        assert_eq!(serialized, format!("\"{}\"", secret.value()));
141
142        let deserialized: SecretString<_> =
143            serde_json::from_str("\"my_secret_password\"").expect("Failed to deserialize");
144        assert_eq!(deserialized, secret);
145    }
146}