secure_gate/
dynamic.rs

1// src/dynamic.rs
2//! Heap-allocated secure wrappers for dynamic secrets.
3//!
4//! `Dynamic<T>` is a zero-cost wrapper around `Box<T>` that:
5//! - Prevents accidental cloning/leaking via `Debug` redaction
6//! - Provides explicit `.expose_secret()` access (the canonical API)
7//! - Supports idiomatic `.into()` conversion from owned values
8//! - Works perfectly with `dynamic_alias!` for beautiful type aliases
9
10use alloc::boxed::Box;
11use core::ops::{Deref, DerefMut};
12
13/// A zero-cost, heap-allocated wrapper for sensitive data.
14///
15/// See module docs for full examples.
16pub struct Dynamic<T: ?Sized>(pub Box<T>);
17
18impl<T: ?Sized> Dynamic<T> {
19    #[inline(always)]
20    pub fn new_boxed(value: Box<T>) -> Self {
21        Dynamic(value)
22    }
23
24    #[inline(always)]
25    pub fn new<U>(value: U) -> Self
26    where
27        U: Into<Box<T>>,
28    {
29        Dynamic(value.into())
30    }
31}
32
33impl<T: ?Sized> Deref for Dynamic<T> {
34    type Target = T;
35    #[inline(always)]
36    fn deref(&self) -> &T {
37        &self.0
38    }
39}
40
41impl<T: ?Sized> DerefMut for Dynamic<T> {
42    #[inline(always)]
43    fn deref_mut(&mut self) -> &mut T {
44        &mut self.0
45    }
46}
47
48impl<T: ?Sized> core::fmt::Debug for Dynamic<T> {
49    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50        f.write_str("[REDACTED]")
51    }
52}
53
54impl<T: ?Sized> Dynamic<T> {
55    /// Access the secret value immutably.
56    ///
57    /// # Examples
58    ///
59    /// ```
60    /// use secure_gate::Dynamic;
61    ///
62    /// let pw: Dynamic<String> = "secret".into();
63    /// assert_eq!(pw.expose_secret(), "secret");
64    /// ```
65    #[inline(always)]
66    pub fn expose_secret(&self) -> &T {
67        &self.0
68    }
69
70    #[inline(always)]
71    pub fn expose_secret_mut(&mut self) -> &mut T {
72        &mut self.0
73    }
74
75    #[deprecated(since = "0.5.5", note = "use `expose_secret` instead")]
76    #[doc(hidden)]
77    #[inline(always)]
78    pub fn view(&self) -> &T {
79        self.expose_secret()
80    }
81
82    #[deprecated(since = "0.5.5", note = "use `expose_secret_mut` instead")]
83    #[doc(hidden)]
84    #[inline(always)]
85    pub fn view_mut(&mut self) -> &mut T {
86        self.expose_secret_mut()
87    }
88
89    #[inline(always)]
90    pub fn into_inner(self) -> Box<T> {
91        self.0
92    }
93}
94
95// Clone impls
96#[cfg(not(feature = "zeroize"))]
97impl<T: Clone> Clone for Dynamic<T> {
98    #[inline(always)]
99    fn clone(&self) -> Self {
100        Dynamic(self.0.clone())
101    }
102}
103
104#[cfg(feature = "zeroize")]
105impl<T: Clone + zeroize::Zeroize> Clone for Dynamic<T> {
106    #[inline(always)]
107    fn clone(&self) -> Self {
108        Dynamic(self.0.clone())
109    }
110}
111
112impl Dynamic<String> {
113    pub fn finish_mut(&mut self) -> &mut String {
114        let s = &mut **self;
115        s.shrink_to_fit();
116        s
117    }
118}
119impl Dynamic<Vec<u8>> {
120    pub fn finish_mut(&mut self) -> &mut Vec<u8> {
121        let v = &mut **self;
122        v.shrink_to_fit();
123        v
124    }
125}
126
127// ——— .into() ergonomics ———
128
129/// Convert any owned value into a heap secret.
130///
131/// # Examples
132///
133/// ```
134/// use secure_gate::Dynamic;
135///
136/// let pw: Dynamic<String> = "hunter2".into();
137/// let data: Dynamic<Vec<u8>> = vec![1, 2, 3].into();
138///
139/// assert_eq!(&*pw, "hunter2");
140/// assert_eq!(data.expose_secret(), &[1, 2, 3]);
141/// ```
142impl<T> From<T> for Dynamic<T>
143where
144    T: Sized,
145{
146    #[inline(always)]
147    fn from(value: T) -> Self {
148        Self(Box::new(value))
149    }
150}
151
152impl<T: ?Sized> From<Box<T>> for Dynamic<T> {
153    #[inline(always)]
154    fn from(boxed: Box<T>) -> Self {
155        Self(boxed)
156    }
157}
158
159/// Convenience: `&str` → password secret.
160///
161/// ```
162/// use secure_gate::Dynamic;
163///
164/// let pw: Dynamic<String> = "correct horse battery staple".into();
165/// assert_eq!(&*pw, "correct horse battery staple");
166/// ```
167impl From<&str> for Dynamic<String> {
168    #[inline(always)]
169    fn from(s: &str) -> Self {
170        Self(Box::new(s.to_string()))
171    }
172}