Skip to main content

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>;