synwire_core/credentials/
secret.rs1use secrecy::{ExposeSecret, SecretString};
4use serde::{Deserialize, Serialize};
5use std::fmt;
6
7#[derive(Clone)]
11pub struct SecretValue {
12 inner: SecretString,
13}
14
15impl SecretValue {
16 pub fn new(value: impl Into<String>) -> Self {
18 Self {
19 inner: SecretString::from(value.into()),
20 }
21 }
22
23 pub fn expose(&self) -> &str {
25 self.inner.expose_secret()
26 }
27}
28
29impl fmt::Debug for SecretValue {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 write!(f, "SecretValue(***)")
32 }
33}
34
35impl fmt::Display for SecretValue {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 write!(f, "***")
38 }
39}
40
41impl Serialize for SecretValue {
42 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
43 where
44 S: serde::Serializer,
45 {
46 serializer.serialize_none()
47 }
48}
49
50impl<'de> Deserialize<'de> for SecretValue {
51 fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
52 where
53 D: serde::Deserializer<'de>,
54 {
55 Ok(Self::new(""))
57 }
58}
59
60impl PartialEq for SecretValue {
61 fn eq(&self, other: &Self) -> bool {
62 self.expose() == other.expose()
63 }
64}
65
66impl Eq for SecretValue {}
67
68impl std::hash::Hash for SecretValue {
69 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
70 self.expose().hash(state);
71 }
72}
73
74#[cfg(test)]
75#[allow(clippy::unwrap_used)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn test_secret_debug_redaction() {
81 let secret = SecretValue::new("my-api-key");
82 let debug = format!("{secret:?}");
83 assert!(!debug.contains("my-api-key"));
84 assert!(debug.contains("***"));
85 }
86
87 #[test]
88 fn test_secret_display_redaction() {
89 let secret = SecretValue::new("my-api-key");
90 let display = format!("{secret}");
91 assert_eq!(display, "***");
92 }
93
94 #[test]
95 fn test_secret_expose() {
96 let secret = SecretValue::new("my-api-key");
97 assert_eq!(secret.expose(), "my-api-key");
98 }
99
100 #[test]
101 fn test_secret_serialize_as_null() {
102 let secret = SecretValue::new("my-api-key");
103 let json = serde_json::to_string(&secret).unwrap();
104 assert_eq!(json, "null");
105 }
106
107 #[test]
108 fn test_secret_equality() {
109 let a = SecretValue::new("key");
110 let b = SecretValue::new("key");
111 let c = SecretValue::new("other");
112 assert_eq!(a, b);
113 assert_ne!(a, c);
114 }
115
116 #[test]
117 fn test_secret_clone() {
118 let secret = SecretValue::new("key");
119 let cloned = secret.clone();
120 assert_eq!(secret.expose(), cloned.expose());
121 }
122}