secure_gate/compat/v08.rs
1//! secrecy **v0.8.0** compatibility layer.
2//!
3//! This module is a near-exact API mirror of [`secrecy`](https://crates.io/crates/secrecy)
4//! v0.8.0 (`edition = "2018"`, `rust-version = "~1.52"`).
5//!
6//! # Drop-in replacement
7//!
8//! The only required change for secrecy 0.8.x users is a mechanical import swap:
9//!
10//! ```text
11//! // Before
12//! use secrecy::{Secret, SecretString, SecretVec, DebugSecret, CloneableSecret, ExposeSecret};
13//!
14//! // After (one global find/replace)
15//! use secure_gate::compat::v08::{Secret, SecretString, SecretVec, DebugSecret};
16//! use secure_gate::compat::{CloneableSecret, ExposeSecret};
17//! ```
18//!
19//! # API table
20//!
21//! | secrecy 0.8 | This module | Notes |
22//! |---|---|---|
23//! | `Secret<S>` | [`Secret<S>`] | Stack/inline; `S: Zeroize` |
24//! | `SecretString` | [`SecretString`] | = `Secret<String>` |
25//! | `SecretVec<T>` | [`SecretVec<T>`] | = `Secret<Vec<T>>` |
26//! | `SecretBox<S>` | [`SecretBox<S>`] | = `Secret<Box<S>>` |
27//! | `ExposeSecret<S>` | [`compat::ExposeSecret`](super::ExposeSecret) | Shared trait |
28//! | `CloneableSecret` | [`compat::CloneableSecret`](super::CloneableSecret) | Shared trait |
29//! | `DebugSecret` | [`DebugSecret`] | v0.8-only trait |
30//! | `SerializableSecret` | [`compat::SerializableSecret`](super::SerializableSecret) | Shared trait |
31//! | `Zeroize` re-export | [`compat::zeroize`](super::zeroize) | Shared re-export |
32//!
33//! # Key differences from v0.10
34//!
35//! - `Secret<S>` is **stack-allocated** (inline `S`) — no `Box`. Use `SecretBox<S>` for
36//! heap-allocated variants.
37//! - No [`ExposeSecretMut`](super::ExposeSecretMut) — mutable access was added in v0.9.
38//! - [`DebugSecret`] trait is required for `Debug` impls. Not present in v0.10.
39//!
40//! # Step-by-step migration
41//!
42//! 1. Replace `secrecy` dependency with `secure-gate` + `features = ["secrecy-compat"]`
43//! 2. Find/replace `use secrecy::` → `use secure_gate::compat::v08::` (types) or
44//! `use secure_gate::compat::` (traits)
45//! 3. Gradually replace `v08::Secret<String>` with [`Dynamic<String>`](crate::Dynamic) using
46//! the provided [`From`] conversions
47//! 4. Replace `v08::Secret<[T; N]>` with [`Fixed<[T; N]>`](crate::Fixed)
48//! 5. Remove `secrecy-compat` feature once fully migrated
49
50extern crate alloc;
51
52use alloc::boxed::Box;
53use alloc::string::String;
54use alloc::vec::Vec;
55use core::str::FromStr;
56use core::{any, fmt};
57use zeroize::Zeroize;
58
59use super::CloneableSecret;
60use super::ExposeSecret;
61#[cfg(feature = "serde-serialize")]
62use super::SerializableSecret;
63
64// ── DebugSecret ───────────────────────────────────────────────────────────────
65
66/// Opt-in debug display for secret types — mirrors `secrecy::DebugSecret`.
67///
68/// Implementing this trait on a type `S` enables `Debug for Secret<S>`, which
69/// will call `S::debug_secret` instead of exposing the value.
70///
71/// The default impl prints `[REDACTED typename]`. Override to customize the label.
72///
73/// # Example
74///
75/// ```rust
76/// # #[cfg(feature = "secrecy-compat")] {
77/// use secure_gate::compat::v08::{DebugSecret, Secret};
78///
79/// struct ApiKey(String);
80/// impl zeroize::Zeroize for ApiKey { fn zeroize(&mut self) { self.0.zeroize(); } }
81/// impl DebugSecret for ApiKey {}
82///
83/// let key = Secret::new(ApiKey(String::from("sk_live_xyz")));
84/// // prints: Secret([REDACTED secrecy_compat_test::ApiKey]) or similar
85/// println!("{:?}", key);
86/// # }
87/// ```
88pub trait DebugSecret {
89 /// Format type-identifying information about the secret (never the value itself).
90 fn debug_secret(f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
91 f.write_str("[REDACTED ")?;
92 f.write_str(any::type_name::<Self>())?;
93 f.write_str("]")
94 }
95}
96
97// Blanket impls mirroring v0.8 — upgraded from size-list macros to const generics.
98impl<T: fmt::Debug, const N: usize> DebugSecret for [T; N] {}
99impl DebugSecret for String {}
100impl<S: DebugSecret + Zeroize> DebugSecret for Box<S> {}
101impl<S: DebugSecret + Zeroize> DebugSecret for Vec<S> {}
102
103// ── Additional CloneableSecret impls (v0.8-specific) ─────────────────────────
104//
105// secrecy 0.8 provides `CloneableSecret` for `String` and `Vec<S: CloneableSecret>`.
106// These are not included in the shared mod.rs (where the trait is defined) because
107// v0.10 deliberately does NOT make `SecretBox<String>` auto-cloneable.
108
109impl CloneableSecret for String {}
110impl<S: CloneableSecret + Zeroize> CloneableSecret for Vec<S> {}
111
112// ── Secret<S> ────────────────────────────────────────────────────────────────
113
114/// Stack/inline secret wrapper — mirrors `secrecy::Secret`.
115///
116/// Stores the secret value **directly** (no heap allocation). On drop, calls
117/// `S::zeroize()` to wipe the memory. Access is only possible through
118/// [`ExposeSecret`](super::ExposeSecret).
119///
120/// # Generic parameter
121///
122/// `S` must implement [`Zeroize`] for secure erasure on drop.
123///
124/// # Debug
125///
126/// `Debug` is only available when `S: DebugSecret`. The output is always
127/// `Secret([REDACTED typename])` — the inner value is never printed.
128///
129/// # Migration to native secure-gate
130///
131/// ```rust
132/// # #[cfg(feature = "secrecy-compat")] {
133/// use secure_gate::compat::v08::Secret;
134/// use secure_gate::Dynamic;
135///
136/// // String → heap-allocated Dynamic<String>
137/// let old: Secret<String> = Secret::new(String::from("hunter2"));
138/// let native: Dynamic<String> = old.into();
139///
140/// // [u8; 32] → stack-allocated Fixed<[u8; 32]>
141/// use secure_gate::Fixed;
142/// let key: Secret<[u8; 32]> = Secret::new([0xABu8; 32]);
143/// let fixed: Fixed<[u8; 32]> = key.into();
144/// # }
145/// ```
146pub struct Secret<S>
147where
148 S: Zeroize,
149{
150 inner_secret: S,
151}
152
153impl<S: Zeroize> Secret<S> {
154 /// Takes ownership of a secret value.
155 pub fn new(secret: S) -> Self {
156 Secret { inner_secret: secret }
157 }
158}
159
160impl<S: Zeroize> ExposeSecret<S> for Secret<S> {
161 fn expose_secret(&self) -> &S {
162 &self.inner_secret
163 }
164}
165
166impl<S: Zeroize> From<S> for Secret<S> {
167 fn from(secret: S) -> Self {
168 Self::new(secret)
169 }
170}
171
172impl<S: CloneableSecret> Clone for Secret<S> {
173 fn clone(&self) -> Self {
174 Secret {
175 inner_secret: self.inner_secret.clone(),
176 }
177 }
178}
179
180impl<S: Zeroize + DebugSecret> fmt::Debug for Secret<S> {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 f.write_str("Secret(")?;
183 S::debug_secret(f)?;
184 f.write_str(")")
185 }
186}
187
188impl<S: Zeroize> Drop for Secret<S> {
189 fn drop(&mut self) {
190 self.inner_secret.zeroize();
191 }
192}
193
194// ── SecretString ─────────────────────────────────────────────────────────────
195
196/// Secret string type — mirrors `secrecy::SecretString` (v0.8).
197///
198/// Type alias for `Secret<String>`. Implements `FromStr`, `Clone` (via
199/// `String: CloneableSecret`), and `Debug` (via `String: DebugSecret`).
200///
201/// Note: secrecy 0.10's `SecretString` is `SecretBox<str>` (different type).
202/// Use [`v10::SecretString`](super::v10::SecretString) when migrating to v0.10 semantics.
203pub type SecretString = Secret<String>;
204
205impl FromStr for SecretString {
206 type Err = core::convert::Infallible;
207
208 fn from_str(src: &str) -> Result<Self, Self::Err> {
209 Ok(SecretString::new(src.to_string()))
210 }
211}
212
213// ── SecretVec ─────────────────────────────────────────────────────────────────
214
215/// Secret vector type — mirrors `secrecy::SecretVec` (v0.8).
216///
217/// Type alias for `Secret<Vec<T>>`.
218pub type SecretVec<T> = Secret<Vec<T>>;
219
220// ── SecretBox ─────────────────────────────────────────────────────────────────
221
222/// Secret boxed type — mirrors `secrecy::SecretBox` (v0.8).
223///
224/// Type alias for `Secret<Box<S>>`. Note that this is **different** from
225/// [`v10::SecretBox`](super::v10::SecretBox), which is a newtype around `Box<S>` with
226/// `?Sized` support. This v0.8 variant stores `Box<S>` as the `S` in `Secret<S>`.
227///
228/// # Zeroize requirement
229///
230/// `Box<S>` must implement [`Zeroize`]. In zeroize ≥ 1.8, this is only provided for
231/// `Box<[Z]>` (heap slices, `Z: Zeroize`) and `Box<str>`. For sized secret buffers,
232/// prefer [`v10::SecretBox`](super::v10::SecretBox) or the native
233/// [`Dynamic<T>`](crate::Dynamic).
234pub type SecretBox<S> = Secret<Box<S>>;
235
236// ── Serde ─────────────────────────────────────────────────────────────────────
237
238#[cfg(feature = "serde-deserialize")]
239impl<'de, T> serde::Deserialize<'de> for Secret<T>
240where
241 T: Zeroize + Clone + serde::de::DeserializeOwned + Sized,
242{
243 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
244 where
245 D: serde::Deserializer<'de>,
246 {
247 T::deserialize(deserializer).map(Secret::new)
248 }
249}
250
251#[cfg(feature = "serde-serialize")]
252impl<T> serde::Serialize for Secret<T>
253where
254 T: Zeroize + SerializableSecret + serde::Serialize + Sized,
255{
256 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
257 where
258 S: serde::Serializer,
259 {
260 self.inner_secret.serialize(serializer)
261 }
262}
263
264// ── Conversions: Secret ↔ Dynamic / Fixed ────────────────────────────────────
265//
266// All conversions require Clone because `Secret<S>` has a `Drop` impl and
267// `#![forbid(unsafe_code)]` prevents us from moving the field out directly.
268// The clone is brief: the original is zeroized when it drops at the end of the
269// conversion function body.
270
271/// Converts `Secret<String>` into a [`Dynamic<String>`](crate::Dynamic).
272impl From<Secret<String>> for crate::Dynamic<String> {
273 fn from(s: Secret<String>) -> Self {
274 crate::Dynamic::new(s.inner_secret.clone())
275 }
276}
277
278/// Converts a [`Dynamic<String>`](crate::Dynamic) into `Secret<String>`.
279impl From<crate::Dynamic<String>> for Secret<String> {
280 fn from(d: crate::Dynamic<String>) -> Self {
281 let val = <crate::Dynamic<String> as crate::RevealSecret>::expose_secret(&d).clone();
282 Secret::new(val)
283 }
284}
285
286/// Converts `Secret<Vec<T>>` into a [`Dynamic<Vec<T>>`](crate::Dynamic).
287impl<T: Clone + Zeroize + 'static> From<Secret<Vec<T>>> for crate::Dynamic<Vec<T>> {
288 fn from(s: Secret<Vec<T>>) -> Self {
289 crate::Dynamic::new(s.inner_secret.clone())
290 }
291}
292
293/// Converts a [`Dynamic<Vec<T>>`](crate::Dynamic) into `Secret<Vec<T>>`.
294impl<T: Clone + Zeroize + 'static> From<crate::Dynamic<Vec<T>>> for Secret<Vec<T>> {
295 fn from(d: crate::Dynamic<Vec<T>>) -> Self {
296 let val = <crate::Dynamic<Vec<T>> as crate::RevealSecret>::expose_secret(&d).clone();
297 Secret::new(val)
298 }
299}
300
301/// Converts `Secret<[T; N]>` into a [`Fixed<[T; N]>`](crate::Fixed).
302impl<T: Clone + Zeroize, const N: usize> From<Secret<[T; N]>> for crate::Fixed<[T; N]> {
303 fn from(s: Secret<[T; N]>) -> Self {
304 let arr = s.inner_secret.clone();
305 crate::Fixed::new(arr)
306 }
307}
308
309/// Converts a [`Fixed<[T; N]>`](crate::Fixed) into `Secret<[T; N]>`.
310impl<T: Clone + Zeroize, const N: usize> From<crate::Fixed<[T; N]>> for Secret<[T; N]> {
311 fn from(f: crate::Fixed<[T; N]>) -> Self {
312 let arr = crate::RevealSecret::expose_secret(&f).clone();
313 Secret::new(arr)
314 }
315}