use std::fmt;
use std::ops::Deref;
use zeroize::Zeroize;
pub struct SecretBytes(Vec<u8>);
impl SecretBytes {
pub fn new(bytes: Vec<u8>) -> Self {
Self(bytes)
}
}
impl Deref for SecretBytes {
type Target = [u8];
fn deref(&self) -> &[u8] {
&self.0
}
}
impl AsRef<[u8]> for SecretBytes {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl fmt::Debug for SecretBytes {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[REDACTED ({} bytes)]", self.0.len())
}
}
impl Drop for SecretBytes {
fn drop(&mut self) {
self.0.zeroize();
}
}
pub struct SecretString(String);
impl SecretString {
pub fn new(s: String) -> Self {
Self(s)
}
}
impl Deref for SecretString {
type Target = str;
fn deref(&self) -> &str {
&self.0
}
}
impl AsRef<str> for SecretString {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Debug for SecretString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[REDACTED ({} bytes)]", self.0.len())
}
}
impl Drop for SecretString {
fn drop(&mut self) {
self.0.zeroize();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn secret_bytes_deref() {
let secret = SecretBytes::new(vec![1, 2, 3]);
assert_eq!(&*secret, &[1, 2, 3]);
}
#[test]
fn secret_bytes_debug_redacts() {
let secret = SecretBytes::new(vec![0xDE, 0xAD]);
let debug = format!("{secret:?}");
assert!(debug.contains("REDACTED"));
assert!(!debug.contains("DE"));
}
#[test]
fn secret_string_deref() {
let secret = SecretString::new("hunter2".into());
assert_eq!(&*secret, "hunter2");
}
#[test]
fn secret_string_debug_redacts() {
let secret = SecretString::new("hunter2".into());
let debug = format!("{secret:?}");
assert!(debug.contains("REDACTED"));
assert!(!debug.contains("hunter2"));
}
#[test]
fn secret_string_as_ref() {
let secret = SecretString::new("test".into());
let s: &str = secret.as_ref();
assert_eq!(s, "test");
}
}