matroskin 0.1.0

whatsminer api client lib
Documentation
//! Secure display utilities for sensitive data.
//!
//! Provides [`Redacted<T>`] wrapper that displays static `[REDACTED]` in `Debug`/`Display`
//! while allowing full access to the inner value via `Deref`. Used for tokens,
//! passwords, salts, encryption keys, and other sensitive fields.
//!
//! # Security
//!
//! - `Debug` and `Display` show static `[REDACTED]`
//! - `Deref` provides read-only access to inner value for legitimate use (crypto, serialization)
//! - `Serialize` delegates to inner — ASIC gets real values
//! - `Zeroize`/`ZeroizeOnDrop` — secure memory cleanup
//! - No `DerefMut` — sensitive data is immutable after construction
//!
//! # Usage
//!
//! ```rust,no_run
//! use matroskin::redacted::Redacted;
//!
//! let token = Redacted("secret_token".to_string());
//! println!("{:?}", token);  // → "[REDACTED]"
//! println!("{}", token.len()); // → 12 (Deref to String)
//! ```

use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use zeroize::{Zeroize, ZeroizeOnDrop};

/// Wrapper that redacts sensitive data in `Debug`/`Display` output.
///
/// Shows static `[REDACTED]` instead of the actual value. Provides read-only
/// access to the inner value via [`Deref`](std::ops::Deref) for legitimate operations
/// (crypto, serialization, etc.).
#[derive(Default, Clone, PartialEq, Eq, Hash)]
pub struct Redacted<T: Zeroize>(pub T);

impl<T: Zeroize> Zeroize for Redacted<T> {
    fn zeroize(&mut self) {
        self.0.zeroize();
    }
}

impl<T: Zeroize> ZeroizeOnDrop for Redacted<T> {}

impl<T: Zeroize> Drop for Redacted<T> {
    fn drop(&mut self) {
        self.zeroize();
    }
}

impl<T: Zeroize> fmt::Debug for Redacted<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("[REDACTED]")
    }
}

impl<T: Zeroize> fmt::Display for Redacted<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("[REDACTED]")
    }
}

impl<T: Zeroize> std::ops::Deref for Redacted<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T: Zeroize + Serialize> Serialize for Redacted<T> {
    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
        self.0.serialize(s)
    }
}

impl<'de, T: Zeroize + Deserialize<'de>> Deserialize<'de> for Redacted<T> {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        T::deserialize(deserializer).map(Self)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn debug_and_display_static_redacted() {
        let secret = Redacted("my_secret_token".to_string());
        assert_eq!(format!("{secret:?}"), "[REDACTED]");
        assert_eq!(format!("{secret}"), "[REDACTED]");
    }

    #[test]
    fn drop_erases_secret_before_releasing_storage() {
        struct Secret<'a>(&'a mut [u8; 8]);

        impl Zeroize for Secret<'_> {
            fn zeroize(&mut self) {
                self.0.zeroize();
            }
        }

        let mut bytes = [42; 8];
        drop(Redacted(Secret(&mut bytes)));
        assert_eq!(bytes, [0; 8]);
    }
}