1#![no_std]
2#![doc = include_str!("../README.md")]
3#![forbid(unsafe_code)]
4#![warn(missing_docs, clippy::pedantic, clippy::cargo)]
5
6use core::fmt;
18
19#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[must_use = "wrapping a value in a secret_fmt is useless if the secret_fmt is immediately dropped"]
25pub struct Secret<T>(T);
26
27impl<T> Secret<T> {
28 #[inline]
37 pub const fn new(value: T) -> Self {
38 Self(value)
39 }
40
41 #[inline]
50 #[must_use = "consuming the secret_fmt to discard the inner value is likely a mistake"]
51 pub fn into_inner(self) -> T {
52 self.0
53 }
54
55 #[inline]
64 #[must_use = "getting the inner reference without using it is likely a mistake"]
65 pub const fn as_inner(&self) -> &T {
66 &self.0
67 }
68
69 #[inline]
79 #[must_use = "getting the inner mutable reference without using it is likely a mistake"]
80 pub fn as_inner_mut(&mut self) -> &mut T {
81 &mut self.0
82 }
83}
84
85impl<T> fmt::Debug for Secret<T> {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 f.write_str("[REDACTED]")
88 }
89}
90
91impl<T> fmt::Display for Secret<T> {
92 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93 f.write_str("[REDACTED]")
94 }
95}
96
97impl<T> From<T> for Secret<T> {
98 #[inline]
99 fn from(value: T) -> Self {
100 Self(value)
101 }
102}
103
104impl<T> AsRef<T> for Secret<T> {
105 #[inline]
106 fn as_ref(&self) -> &T {
107 &self.0
108 }
109}
110
111#[macro_export]
122macro_rules! redact {
123 ($val:expr) => {
124 $crate::Secret::new($val)
125 };
126}
127
128#[cfg(feature = "serde")]
129pub mod serialize_redacted {
130 use super::Secret;
144 use serde::Serializer;
145
146 pub fn serialize<T, S>(_: &Secret<T>, serializer: S) -> Result<S::Ok, S::Error>
151 where
152 S: Serializer,
153 {
154 serializer.serialize_str("[REDACTED]")
155 }
156}
157
158#[cfg(feature = "serde")]
159pub mod serialize_actual {
160 use super::Secret;
174 use serde::{Serialize, Serializer};
175
176 pub fn serialize<T: Serialize, S>(val: &Secret<T>, serializer: S) -> Result<S::Ok, S::Error>
181 where
182 S: Serializer,
183 {
184 val.as_inner().serialize(serializer)
185 }
186}
187
188#[cfg(feature = "serde")]
189impl<'de, T> serde::Deserialize<'de> for Secret<T>
190where
191 T: serde::Deserialize<'de>,
192{
193 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
194 where
195 D: serde::Deserializer<'de>,
196 {
197 T::deserialize(deserializer).map(Self)
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 extern crate std;
207 use std::collections::hash_map::DefaultHasher;
208 use std::format;
209 use std::hash::{Hash, Hasher};
210 use std::string::String;
211
212 #[test]
213 fn test_debug_redacted() {
214 let secret = Secret::new("super_secret_password");
215 assert_eq!(format!("{:?}", secret), "[REDACTED]");
216 }
217
218 #[test]
219 fn test_display_redacted() {
220 let secret = Secret::new(12345);
221 assert_eq!(format!("{}", secret), "[REDACTED]");
222 }
223
224 #[test]
225 fn test_as_inner() {
226 let mut secret = Secret::new(String::from("test"));
227 assert_eq!(secret.as_inner(), "test");
228
229 secret.as_inner_mut().push_str("ing");
230 assert_eq!(secret.into_inner(), "testing");
231 }
232
233 #[test]
234 fn test_derived_traits() {
235 let a = Secret::new(10);
237 let a_clone = a.clone();
238 let b = Secret::new(20);
239
240 assert_eq!(a, a_clone);
241 assert_ne!(a, b);
242 assert!(a < b);
243
244 let mut hasher_a = DefaultHasher::new();
245 a.hash(&mut hasher_a);
246 let mut hasher_inner = DefaultHasher::new();
247 10.hash(&mut hasher_inner);
248 assert_eq!(hasher_a.finish(), hasher_inner.finish());
249 }
250
251 #[test]
252 fn test_macro_and_conversions() {
253 let original = "sensitive_data";
254 let wrapped: Secret<&str> = original.into();
255
256 assert_eq!(wrapped.as_ref(), &"sensitive_data");
257 assert_eq!(format!("{}", redact!(&original)), "[REDACTED]");
258 assert_eq!(format!("{:?}", redact!(original)), "[REDACTED]");
259 }
260
261 #[cfg(feature = "serde")]
262 #[test]
263 fn test_serde() {
264 #[derive(serde::Serialize, serde::Deserialize, PartialEq, core::fmt::Debug)]
265 struct User {
266 id: u32,
267 #[serde(serialize_with = "serialize_redacted::serialize")]
268 api_key: Secret<String>,
269 #[serde(serialize_with = "serialize_actual::serialize")]
270 pass_through: Secret<String>,
271 }
272
273 let json = r#"{"id":123,"api_key":"secret_token_abc","pass_through":"sent_to_stripe"}"#;
274 let user: User = serde_json::from_str(json).unwrap();
275 assert_eq!(user.id, 123);
276 assert_eq!(user.api_key.as_inner(), "secret_token_abc");
277 assert_eq!(user.pass_through.as_inner(), "sent_to_stripe");
278
279 let serialized = serde_json::to_string(&user).unwrap();
280 assert_eq!(
281 serialized,
282 r#"{"id":123,"api_key":"[REDACTED]","pass_through":"sent_to_stripe"}"#
283 );
284 }
285}