secure_gate/compat/v10.rs
1//! secrecy **v0.10.1** compatibility layer.
2//!
3//! This module is a near-exact API mirror of [`secrecy`](https://crates.io/crates/secrecy)
4//! v0.10.1 (`edition = "2021"`, `rust-version = "1.60"`).
5//!
6//! # Drop-in replacement
7//!
8//! The only required change for secrecy 0.10.x users is a mechanical import swap:
9//!
10//! ```text
11//! // Before
12//! use secrecy::{SecretBox, SecretString, SecretSlice, ExposeSecret, ExposeSecretMut};
13//!
14//! // After (one global find/replace)
15//! use secure_gate::compat::v10::{SecretBox, SecretString, SecretSlice};
16//! use secure_gate::compat::{ExposeSecret, ExposeSecretMut};
17//! ```
18//!
19//! # Migration table
20//!
21//! | secrecy 0.10 | secure-gate native |
22//! |---|---|
23//! | `SecretBox<T>` | [`Dynamic<T>`](crate::Dynamic) |
24//! | `SecretString` | `Dynamic<String>` |
25//! | `SecretSlice<T>` | `Dynamic<Vec<T>>` |
26//! | `ExposeSecret<T>` | [`RevealSecret`](crate::RevealSecret) |
27//! | `ExposeSecretMut<T>` | [`RevealSecretMut`](crate::RevealSecretMut) |
28//! | `CloneableSecret` | [`CloneableSecret`](crate::CloneableSecret) (with `cloneable` feature) |
29//! | `SerializableSecret` | [`SerializableSecret`](crate::SerializableSecret) (with `serde-serialize` feature) |
30//!
31//! # Step-by-step migration
32//!
33//! 1. Replace `secrecy` dependency with `secure-gate` + `features = ["secrecy-compat"]`
34//! 2. Find/replace `use secrecy::` → `use secure_gate::compat::v10::` (or `compat::` for traits)
35//! 3. Gradually replace `v10::SecretBox<T>` with [`Dynamic<T>`](crate::Dynamic) using the
36//! provided [`From`] conversions
37//! 4. Replace `compat::ExposeSecret` with [`RevealSecret`](crate::RevealSecret) — bridge impls
38//! on `Dynamic` and `Fixed` mean that call-sites using `.expose_secret()` continue to compile
39//! 5. Remove `secrecy-compat` feature once fully migrated
40
41extern crate alloc;
42
43use alloc::boxed::Box;
44use alloc::string::String;
45use alloc::vec::Vec;
46use core::convert::Infallible;
47use core::str::FromStr;
48use core::{any, fmt};
49use zeroize::{Zeroize, ZeroizeOnDrop};
50
51use super::{CloneableSecret, ExposeSecret, ExposeSecretMut};
52#[cfg(feature = "serde-serialize")]
53use super::SerializableSecret;
54
55// ── SecretBox ────────────────────────────────────────────────────────────────
56
57/// Heap-allocated secret wrapper — mirrors `secrecy::SecretBox`.
58///
59/// Stores the secret in a `Box<S>`, zeroizes on drop, and only exposes the inner
60/// value through [`ExposeSecret`](super::ExposeSecret) /
61/// [`ExposeSecretMut`](super::ExposeSecretMut). `Debug` always prints `[REDACTED]`.
62///
63/// # Migration to native secure-gate
64///
65/// For sized types, convert to [`Dynamic<S>`](crate::Dynamic) using the provided
66/// `From` impl:
67///
68/// ```rust
69/// # #[cfg(feature = "secrecy-compat")] {
70/// use secure_gate::compat::v10::SecretBox;
71/// use secure_gate::Dynamic;
72///
73/// let compat: SecretBox<String> = SecretBox::init_with(|| String::from("hunter2"));
74/// let native: Dynamic<String> = compat.into();
75/// # }
76/// ```
77pub struct SecretBox<S: Zeroize + ?Sized> {
78 inner_secret: Box<S>,
79}
80
81impl<S: Zeroize + ?Sized> Zeroize for SecretBox<S> {
82 fn zeroize(&mut self) {
83 self.inner_secret.as_mut().zeroize();
84 }
85}
86
87impl<S: Zeroize + ?Sized> Drop for SecretBox<S> {
88 fn drop(&mut self) {
89 self.zeroize();
90 }
91}
92
93impl<S: Zeroize + ?Sized> ZeroizeOnDrop for SecretBox<S> {}
94
95impl<S: Zeroize + ?Sized> From<Box<S>> for SecretBox<S> {
96 fn from(source: Box<S>) -> Self {
97 Self::new(source)
98 }
99}
100
101impl<S: Zeroize + ?Sized> SecretBox<S> {
102 /// Creates a `SecretBox` from a pre-boxed value.
103 pub fn new(boxed_secret: Box<S>) -> Self {
104 Self {
105 inner_secret: boxed_secret,
106 }
107 }
108}
109
110impl<S: Zeroize + Default> SecretBox<S> {
111 /// Creates a `SecretBox` by initializing the default value in-place via a mutable closure.
112 pub fn init_with_mut(ctr: impl FnOnce(&mut S)) -> Self {
113 let mut secret = Self::default();
114 ctr(secret.inner_secret.as_mut());
115 secret
116 }
117}
118
119impl<S: Zeroize + Clone> SecretBox<S> {
120 /// Creates a `SecretBox` from the return value of `ctr`.
121 ///
122 /// Makes an effort to zeroize the stack copy before boxing, but this is
123 /// best-effort. Prefer [`init_with_mut`](Self::init_with_mut) when possible.
124 pub fn init_with(ctr: impl FnOnce() -> S) -> Self {
125 let mut data = ctr();
126 let secret = Self {
127 inner_secret: Box::new(data.clone()),
128 };
129 data.zeroize();
130 secret
131 }
132
133 /// Fallible variant of [`init_with`](Self::init_with).
134 pub fn try_init_with<E>(ctr: impl FnOnce() -> Result<S, E>) -> Result<Self, E> {
135 let mut data = ctr()?;
136 let secret = Self {
137 inner_secret: Box::new(data.clone()),
138 };
139 data.zeroize();
140 Ok(secret)
141 }
142}
143
144impl<S: Zeroize + Default> Default for SecretBox<S> {
145 fn default() -> Self {
146 Self {
147 inner_secret: Box::<S>::default(),
148 }
149 }
150}
151
152impl<S: Zeroize + ?Sized> fmt::Debug for SecretBox<S> {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 write!(f, "SecretBox<{}>([REDACTED])", any::type_name::<S>())
155 }
156}
157
158impl<S: CloneableSecret> Clone for SecretBox<S> {
159 fn clone(&self) -> Self {
160 SecretBox {
161 inner_secret: self.inner_secret.clone(),
162 }
163 }
164}
165
166impl<S: Zeroize + ?Sized> ExposeSecret<S> for SecretBox<S> {
167 fn expose_secret(&self) -> &S {
168 self.inner_secret.as_ref()
169 }
170}
171
172impl<S: Zeroize + ?Sized> ExposeSecretMut<S> for SecretBox<S> {
173 fn expose_secret_mut(&mut self) -> &mut S {
174 self.inner_secret.as_mut()
175 }
176}
177
178// ── SecretString ─────────────────────────────────────────────────────────────
179
180/// Secret string type — mirrors `secrecy::SecretString`.
181///
182/// Type alias for `SecretBox<str>`. Construct from [`String`] or `&str`.
183/// Prefer [`Dynamic<String>`](crate::Dynamic) for new code.
184pub type SecretString = SecretBox<str>;
185
186impl From<String> for SecretString {
187 fn from(s: String) -> Self {
188 Self::from(s.into_boxed_str())
189 }
190}
191
192impl<'a> From<&'a str> for SecretString {
193 fn from(s: &'a str) -> Self {
194 Self::from(String::from(s))
195 }
196}
197
198impl FromStr for SecretString {
199 type Err = Infallible;
200
201 fn from_str(s: &str) -> Result<Self, Self::Err> {
202 Ok(Self::from(s))
203 }
204}
205
206impl Clone for SecretString {
207 fn clone(&self) -> Self {
208 SecretBox {
209 inner_secret: self.inner_secret.clone(),
210 }
211 }
212}
213
214impl Default for SecretString {
215 fn default() -> Self {
216 String::default().into()
217 }
218}
219
220// ── SecretSlice ───────────────────────────────────────────────────────────────
221
222/// Secret slice type — mirrors `secrecy::SecretSlice`.
223///
224/// Type alias for `SecretBox<[S]>`. Construct from [`Vec<S>`].
225/// Prefer [`Dynamic<Vec<S>>`](crate::Dynamic) for new code.
226pub type SecretSlice<S> = SecretBox<[S]>;
227
228impl<S> From<Vec<S>> for SecretSlice<S>
229where
230 S: Zeroize,
231 [S]: Zeroize,
232{
233 fn from(vec: Vec<S>) -> Self {
234 Self::from(vec.into_boxed_slice())
235 }
236}
237
238impl<S> Clone for SecretSlice<S>
239where
240 S: CloneableSecret + Zeroize,
241 [S]: Zeroize,
242{
243 fn clone(&self) -> Self {
244 SecretBox {
245 inner_secret: Vec::from(&*self.inner_secret).into_boxed_slice(),
246 }
247 }
248}
249
250impl<S> Default for SecretSlice<S>
251where
252 S: Zeroize,
253 [S]: Zeroize,
254{
255 fn default() -> Self {
256 Vec::<S>::new().into()
257 }
258}
259
260// ── Conversions: SecretBox ↔ Dynamic ─────────────────────────────────────────
261
262/// Converts a `SecretBox<S>` into a [`Dynamic<S>`](crate::Dynamic) (primary migration path).
263///
264/// Requires `S: Clone` because the inner `Box<S>` cannot be moved out of `SecretBox`
265/// without unsafe code (`SecretBox` has a `Drop` impl). The clone is immediately
266/// wrapped in `Dynamic` and the original is zeroized on drop.
267///
268/// For zero-copy migration, construct `Dynamic<S>` directly instead.
269impl<S: Clone + Zeroize + 'static> From<SecretBox<S>> for crate::Dynamic<S> {
270 fn from(sb: SecretBox<S>) -> Self {
271 crate::Dynamic::new(sb.inner_secret.as_ref().clone())
272 }
273}
274
275/// Converts a [`Dynamic<String>`](crate::Dynamic) back into a `SecretBox<String>`.
276///
277/// Clones the inner `String`. Both the source and the new wrapper are zeroized on drop.
278impl From<crate::Dynamic<String>> for SecretBox<String> {
279 fn from(d: crate::Dynamic<String>) -> Self {
280 let val = <crate::Dynamic<String> as crate::RevealSecret>::expose_secret(&d).clone();
281 SecretBox::new(Box::new(val))
282 }
283}
284
285/// Converts a [`Dynamic<String>`](crate::Dynamic) into a `SecretString` (= `SecretBox<str>`).
286///
287/// Clones the inner string. Both ends are zeroized on drop.
288impl From<crate::Dynamic<String>> for SecretString {
289 fn from(d: crate::Dynamic<String>) -> Self {
290 let val = <crate::Dynamic<String> as crate::RevealSecret>::expose_secret(&d).clone();
291 SecretString::from(val)
292 }
293}
294
295/// Converts a [`Dynamic<Vec<S>>`](crate::Dynamic) back into a `SecretBox<Vec<S>>`.
296///
297/// Clones the inner `Vec`. Both ends are zeroized on drop.
298impl<S: Clone + Zeroize + 'static> From<crate::Dynamic<Vec<S>>> for SecretBox<Vec<S>> {
299 fn from(d: crate::Dynamic<Vec<S>>) -> Self {
300 let val = <crate::Dynamic<Vec<S>> as crate::RevealSecret>::expose_secret(&d).clone();
301 SecretBox::new(Box::new(val))
302 }
303}
304
305/// Converts a `SecretString` (= `SecretBox<str>`) into a [`Dynamic<String>`](crate::Dynamic).
306///
307/// Clones the inner `str` into a new `String`. Both ends are zeroized on drop.
308impl From<SecretString> for crate::Dynamic<String> {
309 fn from(sb: SecretString) -> Self {
310 let val = String::from(sb.inner_secret.as_ref());
311 crate::Dynamic::new(val)
312 }
313}
314
315// ── Serde ─────────────────────────────────────────────────────────────────────
316
317#[cfg(feature = "serde-deserialize")]
318impl<'de, T> serde::Deserialize<'de> for SecretBox<T>
319where
320 T: Zeroize + Clone + serde::de::DeserializeOwned + Sized,
321{
322 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
323 where
324 D: serde::Deserializer<'de>,
325 {
326 Self::try_init_with(|| T::deserialize(deserializer))
327 }
328}
329
330#[cfg(feature = "serde-deserialize")]
331impl<'de> serde::Deserialize<'de> for SecretString {
332 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
333 where
334 D: serde::Deserializer<'de>,
335 {
336 String::deserialize(deserializer).map(Into::into)
337 }
338}
339
340#[cfg(feature = "serde-serialize")]
341impl<T> serde::Serialize for SecretBox<T>
342where
343 T: Zeroize + SerializableSecret + serde::Serialize + Sized,
344{
345 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
346 where
347 S: serde::Serializer,
348 {
349 self.inner_secret.as_ref().serialize(serializer)
350 }
351}
352
353// ── Legacy alias ─────────────────────────────────────────────────────────────
354
355/// Legacy type alias for [`SecretBox`] — mirrors `secrecy::Secret` from secrecy <0.9.
356///
357/// secrecy 0.9 renamed `Secret<T>` to `SecretBox<T>`. Use [`SecretBox`] instead.
358///
359/// **Note:** secrecy 0.8 users should use [`v08::Secret`](super::v08::Secret) instead,
360/// which mirrors the original stack-allocated semantics.
361#[deprecated(since = "0.8.0", note = "Use `SecretBox` instead (mirrors secrecy >=0.9)")]
362pub type Secret<S> = SecretBox<S>;