1use serde::{Deserialize, Serialize};
6use std::fmt;
7
8#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct SecretString(String);
12
13impl SecretString {
14 pub fn new(s: impl Into<String>) -> Self {
15 Self(s.into())
16 }
17
18 pub fn expose(&self) -> &str {
20 &self.0
21 }
22
23 pub fn into_inner(self) -> String {
24 self.0
25 }
26
27 pub fn is_empty(&self) -> bool {
28 self.0.is_empty()
29 }
30}
31
32impl fmt::Debug for SecretString {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 f.write_str("SecretString(***)")
35 }
36}
37
38impl fmt::Display for SecretString {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 f.write_str("***")
41 }
42}
43
44impl From<String> for SecretString {
45 fn from(s: String) -> Self {
46 Self(s)
47 }
48}
49
50impl From<&str> for SecretString {
51 fn from(s: &str) -> Self {
52 Self(s.to_string())
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59
60 #[test]
61 fn secret_never_in_debug_or_display() {
62 let s = SecretString::new("sk-super-secret");
63 let d = format!("{s:?}");
64 let disp = format!("{s}");
65 assert!(!d.contains("sk-super-secret"));
66 assert!(!disp.contains("sk-super-secret"));
67 assert!(d.contains("***"));
68 assert_eq!(s.expose(), "sk-super-secret");
69 }
70}