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 access via `.expose_secret()` (canonical API).
7//! - Supports idiomatic `.into()` conversions from owned values.
8//! - Works seamlessly with [`dynamic_alias!`] for type aliases.
9//!
10//! # Examples
11//!
12//! ```
13//! use secure_gate::{dynamic_alias, Dynamic};
14//!
15//! dynamic_alias!(Password, String);
16//!
17//! let pw: Password = "hunter2".into();
18//! assert_eq!(pw.expose_secret(), "hunter2");
19//! ```
20
21extern crate alloc;
22
23use alloc::boxed::Box;
24use core::ops::{Deref, DerefMut};
25
26/// A zero-cost, heap-allocated wrapper for sensitive data.
27///
28/// `Dynamic<T>` stores its value on the heap via `Box<T>`. It behaves like `T`
29/// thanks to `Deref`/`DerefMut`, but redacts itself in debug output and requires
30/// explicit access to the inner value.
31///
32/// Use this for dynamic-sized secrets like passwords or variable-length keys.
33///
34/// # Examples
35///
36/// ```
37/// use secure_gate::Dynamic;
38///
39/// let secret: Dynamic<Vec<u8>> = vec![1, 2, 3].into();
40/// assert_eq!(secret.expose_secret(), &[1, 2, 3]);
41/// ```
42pub struct Dynamic<T: ?Sized>(pub Box<T>);
43
44impl<T: ?Sized> Dynamic<T> {
45 /// Creates a new `Dynamic` from a boxed value.
46 ///
47 /// # Examples
48 ///
49 /// ```
50 /// use secure_gate::Dynamic;
51 ///
52 /// let secret = Dynamic::new_boxed(Box::new("hello".to_string()));
53 /// assert_eq!(secret.expose_secret(), "hello");
54 /// ```
55 #[inline(always)]
56 pub fn new_boxed(value: Box<T>) -> Self {
57 Dynamic(value)
58 }
59
60 /// Creates a new `Dynamic` from a value that can be converted into `Box<T>`.
61 ///
62 /// # Examples
63 ///
64 /// ```
65 /// use secure_gate::Dynamic;
66 ///
67 /// let secret: Dynamic<String> = Dynamic::new("hunter2".to_string());
68 /// assert_eq!(secret.expose_secret(), "hunter2");
69 /// ```
70 #[inline(always)]
71 pub fn new<U>(value: U) -> Self
72 where
73 U: Into<Box<T>>,
74 {
75 Dynamic(value.into())
76 }
77}
78
79impl<T: ?Sized> Deref for Dynamic<T> {
80 type Target = T;
81
82 /// Dereferences the wrapper to access the inner value immutably.
83 #[inline(always)]
84 fn deref(&self) -> &T {
85 &self.0
86 }
87}
88
89impl<T: ?Sized> DerefMut for Dynamic<T> {
90 /// Dereferences the wrapper mutably to access the inner value.
91 #[inline(always)]
92 fn deref_mut(&mut self) -> &mut T {
93 &mut self.0
94 }
95}
96
97impl<T: ?Sized> core::fmt::Debug for Dynamic<T> {
98 /// Formats the value as "[REDACTED]" to prevent leakage in debug output.
99 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100 f.write_str("[REDACTED]")
101 }
102}
103
104impl<T: ?Sized> Dynamic<T> {
105 /// Accesses the secret value immutably.
106 ///
107 /// This is the canonical, explicit way to read the secret.
108 ///
109 /// # Examples
110 ///
111 /// ```
112 /// use secure_gate::Dynamic;
113 ///
114 /// let secret: Dynamic<String> = "secret".into();
115 /// assert_eq!(secret.expose_secret(), "secret");
116 /// ```
117 #[inline(always)]
118 pub fn expose_secret(&self) -> &T {
119 &self.0
120 }
121
122 /// Accesses the secret value mutably.
123 ///
124 /// Use for in-place modifications.
125 ///
126 /// # Examples
127 ///
128 /// ```
129 /// use secure_gate::Dynamic;
130 ///
131 /// let mut secret: Dynamic<String> = "hello".into();
132 /// secret.expose_secret_mut().push('!');
133 /// assert_eq!(secret.expose_secret(), "hello!");
134 /// ```
135 #[inline(always)]
136 pub fn expose_secret_mut(&mut self) -> &mut T {
137 &mut self.0
138 }
139
140 /// **Deprecated**: Use [`expose_secret`] instead.
141 ///
142 /// This method forwards to [`expose_secret`] for compatibility.
143 #[deprecated(since = "0.5.5", note = "use `expose_secret` instead")]
144 #[doc(hidden)]
145 #[inline(always)]
146 pub fn view(&self) -> &T {
147 self.expose_secret()
148 }
149
150 /// **Deprecated**: Use [`expose_secret_mut`] instead.
151 ///
152 /// This method forwards to [`expose_secret_mut`] for compatibility.
153 #[deprecated(since = "0.5.5", note = "use `expose_secret_mut` instead")]
154 #[doc(hidden)]
155 #[inline(always)]
156 pub fn view_mut(&mut self) -> &mut T {
157 self.expose_secret_mut()
158 }
159
160 /// Consumes the wrapper and returns the inner boxed value.
161 ///
162 /// # Examples
163 ///
164 /// ```
165 /// use secure_gate::Dynamic;
166 ///
167 /// let secret: Dynamic<String> = "owned".into();
168 /// let owned: Box<String> = secret.into_inner();
169 /// assert_eq!(&*owned, "owned");
170 /// ```
171 #[inline(always)]
172 pub fn into_inner(self) -> Box<T> {
173 self.0
174 }
175}
176
177// Clone impls
178#[cfg(not(feature = "zeroize"))]
179impl<T: Clone> Clone for Dynamic<T> {
180 /// Clones the wrapper, cloning the inner value.
181 #[inline(always)]
182 fn clone(&self) -> Self {
183 Dynamic(self.0.clone())
184 }
185}
186
187#[cfg(feature = "zeroize")]
188impl<T: Clone + zeroize::Zeroize> Clone for Dynamic<T> {
189 /// Clones the wrapper, cloning the inner value.
190 #[inline(always)]
191 fn clone(&self) -> Self {
192 Dynamic(self.0.clone())
193 }
194}
195
196impl Dynamic<String> {
197 /// Shrinks the string's capacity to fit its length and returns a mutable reference.
198 ///
199 /// Use this to eliminate slack memory after mutations.
200 ///
201 /// # Examples
202 ///
203 /// ```
204 /// use secure_gate::Dynamic;
205 ///
206 /// let mut secret: Dynamic<String> = String::with_capacity(100).into();
207 /// secret.push_str("short");
208 /// let s: &mut String = secret.finish_mut();
209 /// assert_eq!(s.capacity(), 5);
210 /// ```
211 pub fn finish_mut(&mut self) -> &mut String {
212 let s = &mut **self;
213 s.shrink_to_fit();
214 s
215 }
216}
217
218impl Dynamic<Vec<u8>> {
219 /// Shrinks the vector's capacity to fit its length and returns a mutable reference.
220 ///
221 /// Use this to eliminate slack memory after mutations.
222 ///
223 /// # Examples
224 ///
225 /// ```
226 /// use secure_gate::Dynamic;
227 ///
228 /// let mut secret: Dynamic<Vec<u8>> = Vec::with_capacity(100).into();
229 /// secret.extend_from_slice(b"short");
230 /// let v: &mut Vec<u8> = secret.finish_mut();
231 /// assert_eq!(v.capacity(), 5);
232 /// ```
233 pub fn finish_mut(&mut self) -> &mut Vec<u8> {
234 let v = &mut **self;
235 v.shrink_to_fit();
236 v
237 }
238}
239
240// ——— .into() ergonomics ———
241/// Converts an owned value into a `Dynamic`.
242///
243/// # Examples
244///
245/// ```
246/// use secure_gate::Dynamic;
247///
248/// let secret: Dynamic<Vec<u8>> = vec![1, 2, 3].into();
249/// assert_eq!(secret.expose_secret(), &[1, 2, 3]);
250/// ```
251impl<T> From<T> for Dynamic<T>
252where
253 T: Sized,
254{
255 #[inline(always)]
256 fn from(value: T) -> Self {
257 Self(Box::new(value))
258 }
259}
260
261/// Converts a `Box<T>` into a `Dynamic<T>`.
262impl<T: ?Sized> From<Box<T>> for Dynamic<T> {
263 #[inline(always)]
264 fn from(boxed: Box<T>) -> Self {
265 Self(boxed)
266 }
267}
268
269/// Convenience conversion from `&str` to `Dynamic<String>`.
270///
271/// # Examples
272///
273/// ```
274/// use secure_gate::Dynamic;
275///
276/// let secret: Dynamic<String> = "password".into();
277/// assert_eq!(secret.expose_secret(), "password");
278/// ```
279impl From<&str> for Dynamic<String> {
280 #[inline(always)]
281 fn from(s: &str) -> Self {
282 Self(Box::new(s.to_string()))
283 }
284}
285
286// ───── Add PartialEq and Eq impls for Dynamic ─────
287/// Implements PartialEq for Dynamic<T> where T implements PartialEq.
288///
289/// This enables comparison on Dynamic types like Dynamic<String> or Dynamic<Vec<u8>>.
290impl<T: PartialEq + ?Sized> PartialEq for Dynamic<T> {
291 #[inline(always)]
292 fn eq(&self, other: &Self) -> bool {
293 **self == **other
294 }
295}
296
297/// Implements Eq for Dynamic<T> where T implements Eq.
298impl<T: Eq + ?Sized> Eq for Dynamic<T> {}