secure_gate/macros/dynamic_newtype.rs
1//! `dynamic_newtype!` — nominal newtype over `Dynamic<T>`.
2//!
3//! Ships in 0.9.0; backported to the 0.8 line in 0.8.0-rc.11. Design record:
4//! [`docs/design/nominal_newtypes.md`](https://github.com/Slurp9187/secure-gate/blob/main/docs/design/nominal_newtypes.md) — a repository file, not part of the
5//! published crate, so this is a link and not a path. It describes `main`.
6
7/// Creates a distinct nominal type wrapping [`Dynamic<T>`](crate::Dynamic).
8///
9/// Generates a `struct` rather than a `type` alias: two `dynamic_newtype!`
10/// types over the same inner type are distinct, where two plain `type`
11/// aliases over [`Dynamic<T>`](crate::Dynamic) are one type.
12///
13/// # Syntax
14///
15/// ```text
16/// dynamic_newtype!(pub Name, String); // shaped: full String API
17/// dynamic_newtype!(pub Name, Vec<u8>); // shaped: full byte API
18/// dynamic_newtype!(pub Name, generic T); // reduced API, opted into
19/// dynamic_newtype!(pub(crate) Name, String); // crate-visible
20/// dynamic_newtype!(Name, String); // private
21/// dynamic_newtype!(pub Name, String, "doc string"); // with custom doc
22/// dynamic_newtype!(pub Name, String, derive: [ConstantTimeEq]);
23/// dynamic_newtype!(pub Name, String, "doc", derive: [ConstantTimeEq]);
24/// ```
25///
26/// Supported `derive:` options are `ConstantTimeEq`, `Deserialize`, `FromWrapper`, `IntoWrapper`, and `WrapperAccess` (= both directions);
27/// `Clone` and `Serialize` are deliberately absent — see
28/// [`fixed_newtype!`](crate::fixed_newtype) for the reasoning and the
29/// hand-written pattern, which applies identically here.
30///
31/// # The `"doc string"` slot takes exactly one string literal
32///
33/// That argument is matched as `$doc:literal`, so it accepts a single string
34/// literal and nothing else. `concat!("a ", "b")` is a macro call, not a
35/// literal, and will not match — nor will a `const`, or pieces joined together
36/// at compile time.
37///
38/// **The error you get is misleading.** When the doc argument fails to match,
39/// the input falls through to the catch-all arm, which reports that the *inner
40/// type* is not one of the shaped types and suggests writing `String` or
41/// `Vec<u8>` literally. The inner type is usually fine; the doc slot is what
42/// failed. If you see that message on a call whose inner type is plainly
43/// `String` or `Vec<u8>`, look at the doc argument first.
44///
45/// For anything longer than one literal — multiple paragraphs, generated text,
46/// `#[doc = ...]` — put ordinary attributes before the name instead. The macro
47/// accepts `$(#[$attr:meta])*` there, so normal `///` comments work and are the
48/// better style for real prose:
49///
50/// ```rust
51/// # #[cfg(feature = "alloc")] {
52/// use secure_gate::dynamic_newtype;
53///
54/// dynamic_newtype!(
55/// /// First line of prose.
56/// ///
57/// /// A second paragraph, as many lines as you like.
58/// pub SessionToken, String
59/// );
60/// # let _ = SessionToken::new(String::from("x"));
61/// # }
62/// ```
63///
64/// The `"doc string"` form remains for short one-liners and for callers
65/// generating the whole invocation from another macro.
66///
67/// # Inner types must be written literally
68///
69/// Macros match **tokens**, not resolved types. `String` and `Vec<u8>` are
70/// matched as literal tokens, so they must be spelled exactly that way to get
71/// the full API for their shape:
72///
73/// ```rust
74/// # #[cfg(feature = "alloc")] {
75/// use secure_gate::{dynamic_newtype, SecretLen};
76///
77/// dynamic_newtype!(pub ApiKey, String); // full String API
78/// dynamic_newtype!(pub Token, Vec<u8>); // full byte API: hex, io::Write, …
79///
80/// let k: ApiKey = "sk_live".into();
81/// assert_eq!(k.len(), 7);
82/// # }
83/// ```
84///
85/// A type alias (`type MyStr = String`) or a fully-qualified path
86/// (`std::string::String`) is a *different token sequence*, so it cannot reach
87/// those arms — and no macro can see through it, because macro expansion runs
88/// before type resolution. Rather than silently hand back a newtype missing
89/// half its API, such input is a **compile error** naming the fix.
90///
91/// `generic` is **not** a way to get past that error. `generic Vec<u8>` and
92/// `generic String` are themselves a compile error, because each is the same
93/// growable payload as the shaped arm with strictly less API -- for `Vec<u8>`,
94/// no `io::Write`, which is the only growth path that wipes the buffer it
95/// abandons. Delete the word and the safe surface comes with it.
96///
97/// That reject matches literal tokens too, so it is a guard on the *spelling*,
98/// not on the type: `generic MyBytes` and `generic alloc::vec::Vec<u8>` are
99/// different token sequences and still land on the reduced arm, carrying a
100/// growable payload with no safe way to grow it. The macro cannot see through
101/// an alias or a path -- that is the same limitation as above, not a separate
102/// one -- and a token deny-list does not become type-level by getting longer.
103/// If the inner type is a `String` or a `Vec<u8>` under any spelling, write the
104/// literal token.
105///
106/// When the inner type genuinely is something else, opt in with `generic`:
107///
108/// ```rust
109/// # #[cfg(feature = "alloc")] {
110/// use secure_gate::{dynamic_newtype, RevealSecret};
111///
112/// dynamic_newtype!(pub Counters, generic Vec<u32>);
113///
114/// let c = Counters::new(vec![1u32, 2, 3]);
115/// assert_eq!(c.with_secret(|v| v.len()), 3);
116/// # }
117/// ```
118///
119/// The `generic` form deliberately provides less: [`RevealSecret`](crate::RevealSecret),
120/// [`RevealSecretMut`](crate::RevealSecretMut), redacted `Debug`, `Zeroize`,
121/// `ZeroizeOnDrop`, and `new` — no [`SecretLen`](crate::SecretLen) and no
122/// encoders. The reason is what the macro can see rather than what each method
123/// means: the inner type arrives as one opaque token, so the expansion cannot
124/// tell a `Vec` from a struct and withholds the shape-dependent surface
125/// uniformly. An encoder genuinely has no meaning for an arbitrary inner type;
126/// `SecretLen` does, and the base wrapper implements it for every
127/// `Dynamic<Vec<T>>` with a correct `byte_len`, so `derive: [IntoWrapper]`
128/// reaches it through `as_wrapper()`. What the arm withholds is the newtype's
129/// own `len()`, not the answer. Writing the marker is how you say you know
130/// that.
131///
132/// It also has no `new_with`, which the `String` and `Vec<u8>` arms do get.
133/// [`fixed_newtype!`](crate::fixed_newtype)'s generic arm *does* forward
134/// [`Fixed::new_with`](crate::Fixed::new_with), because that constructor now
135/// exists for any `T: SentinelValue`. `Dynamic::new_with` is still shaped —
136/// `FnOnce(&mut String)` / `FnOnce(&mut Vec<u8>)` — so there is nothing here
137/// to forward for an arbitrary inner type. `new` takes its value by value;
138/// build it as close to the call as you can.
139///
140/// The two macros are also asymmetric about `SecretLen`, deliberately.
141/// `fixed_newtype!` has a third arm for a *literal* array — `generic [i16; 256]` —
142/// which forwards `SecretLen` because the element type and the length are both
143/// visible as tokens. There is no equivalent here and none is wanted: a `Dynamic`
144/// payload carries its length at runtime rather than in the type, so there is no
145/// token for an arm to read, and `derive: [IntoWrapper]` remains the way to reach
146/// the base wrapper's own `len()`.
147///
148/// # Implementation Notes
149///
150/// The generated type is `#[repr(transparent)]` over the wrapper and delegates
151/// through `#[inline]` methods, so it costs nothing at runtime.
152///
153/// Unlike [`Fixed`](crate::Fixed), there is **no compile-time zero-size check**
154/// here, and the reason is about the payload rather than the wrapper. For
155/// `String` and `Vec<u8>` emptiness is a runtime property — an empty one is a
156/// legitimate value to hold before validation — so there is nothing for a
157/// compile-time check to decide. That leaves one real gap: a statically
158/// zero-sized inner type. `Dynamic<Zst>` for a zero-sized `Zst` constructs
159/// happily, where `Fixed<Zst>` is now rejected. Validate expected lengths in
160/// your own tests, and do not reach for a zero-sized inner type expecting to be
161/// stopped.
162///
163/// **Do not add your own `Drop` impl.** None is needed: the wrapped
164/// [`Dynamic`](crate::Dynamic) still runs its own, so zeroization is
165/// unaffected. Adding one makes the inner field unmovable (**E0509**) and
166/// silently costs you [`into_inner`](crate::RevealSecret::into_inner).
167///
168/// # Security
169///
170/// Generated types inherit every [`Dynamic`](crate::Dynamic) guarantee:
171/// zeroize on drop including spare capacity, `Debug` that always prints
172/// `[REDACTED]`, and access only through
173/// [`RevealSecret`](crate::RevealSecret) /
174/// [`RevealSecretMut`](crate::RevealSecretMut). There is no `Deref`, so a
175/// generated newtype is not coercible to its wrapper — which is what keeps the
176/// nominal separation total rather than by-value-only.
177///
178/// **Nominal separation guards against mistakes, not intent** — but nothing is
179/// generated that would undo it by accident. There is no `From<Wrapper>` and
180/// no `Deref`, so a base-typed value cannot flow into a newtype through
181/// `.into()`, and `&Newtype` never coerces to `&Wrapper` at a call site. By
182/// default the only way material enters or leaves is the 3-tier access API —
183/// a `with_secret` round trip — which is explicit and shows up in the audit
184/// sweep. Base-wrapper access is opt-in, per newtype, and **split by
185/// direction**: `derive: [FromWrapper]` adds `from_wrapper` (a base value
186/// *enters* this role), `derive: [IntoWrapper]` adds `as_wrapper`,
187/// `as_wrapper_mut`, and `into_wrapper` (material *leaves* this role toward
188/// the base type), and `derive: [WrapperAccess]` is shorthand for both.
189///
190/// These cross a different wall from `into_inner`. A newtype has two:
191///
192/// | Wall | Going in | Going out |
193/// |---|---|---|
194/// | Contents (the wrapper's protection) | `new`, `From<&str>` / `From<&[u8]>` | `with_secret`, `expose_secret`, `into_inner` |
195/// | Role (the nominal label) | `from_wrapper` | `as_wrapper`, `as_wrapper_mut`, `into_wrapper` |
196///
197/// `into_inner` leaves the protection: it hands back the plain value, so the
198/// contents are in the caller's hands (tier 3 of the access model, audited).
199/// `into_wrapper` is a **label** drop; `into_inner` is a **protection** drop.
200/// Confusing those two names is the main way this model gets misread. `into_wrapper` only removes the label: the
201/// result is still a `Dynamic`, still unreadable without `with_secret`. The
202/// role row exists so that dropping a label never forces opening the
203/// contents — without it, reaching base-typed code costs an `into_inner` plus
204/// a rebuild, a reveal the job never needed.
205///
206/// **Which direction is safe depends on the pool.** In a mixed tree the base
207/// type is not raw material: every plain `type` alias sharing it *is* that
208/// type, so a directional token connects this role to all of them at once.
209/// `FromWrapper` lets anything in the pool become this role — the source never
210/// opts in, because the source is just the base type — so a type that guards
211/// a boundary must never take it. `IntoWrapper` lets this role become anything
212/// in the pool, which is safe only when the role is no more sensitive than
213/// the least-sensitive plain alias sharing its base; on a secret role it is an
214/// explicit, greppable downgrade, not a neutral operation. The default,
215/// neither token, is sufficient more often than it looks. The exposure is
216/// largest during a partial migration — the regime real consumers live in —
217/// because while most of the pool stays a plain `type` alias, the base type
218/// is a universal donor. Audit these methods the way you audit
219/// `expose_secret()`.
220///
221/// The heap caveats of [`Dynamic`](crate::Dynamic) carry over unchanged: see
222/// `SECURITY.md` on realloc residue for `Vec`/`String` growth after wrapping.
223///
224/// # See also
225///
226/// - [`fixed_newtype!`](crate::fixed_newtype) — stack-allocated counterpart,
227/// and the reference for the `derive:` rules
228/// - a plain `type` alias — `pub type Password = Dynamic<String>;` — when a
229/// readable name is all that is wanted and interchangeability with the base
230/// type is a feature rather than a risk; the crate's README argues when that
231/// is the right reach
232#[cfg(feature = "alloc")]
233#[macro_export]
234macro_rules! dynamic_newtype {
235 // ---- `generic` applied to a shape that has its own arm: rejected ----
236 //
237 // `generic Vec<u8>` and `generic String` are strictly dominated spellings.
238 // They take a payload this crate already knows how to grow safely and then
239 // withhold the method that does it: the `generic` arm emits only
240 // `__sg_newtype_base!` plus a constructor, so the shaped arms' `SecretLen`,
241 // `From`, `new_with`, encoders, and -- for `Vec<u8>` under `std` -- the
242 // `io::Write` impl that wipes the buffer it abandons, are all absent. Same
243 // payload, strictly less API, and no reason to write it.
244 //
245 // These must precede every `generic $inner:ty` arm, for the reason the
246 // comment below gives: once a `:ty` fragment is parsed there is no
247 // backtracking. They match literal tokens only, exactly as the shaped arms
248 // do, so a type alias or a path-qualified spelling
249 // (`alloc::vec::Vec<u8>`) is a different token sequence and is NOT caught.
250 // That remainder is documented rather than chased; a token deny-list does
251 // not become type-level by getting longer.
252 ($(#[$attr:meta])* $vis:vis $name:ident, generic Vec<u8>, $doc:literal, derive: [$($opt:ident),* $(,)?]) => {
253 $crate::__sg_dynamic_generic_is_shaped!(@bytes);
254 };
255 ($(#[$attr:meta])* $vis:vis $name:ident, generic Vec<u8>, $doc:literal) => {
256 $crate::__sg_dynamic_generic_is_shaped!(@bytes);
257 };
258 ($(#[$attr:meta])* $vis:vis $name:ident, generic Vec<u8>, derive: [$($opt:ident),* $(,)?]) => {
259 $crate::__sg_dynamic_generic_is_shaped!(@bytes);
260 };
261 ($(#[$attr:meta])* $vis:vis $name:ident, generic Vec<u8>) => {
262 $crate::__sg_dynamic_generic_is_shaped!(@bytes);
263 };
264 ($(#[$attr:meta])* $vis:vis $name:ident, generic String, $doc:literal, derive: [$($opt:ident),* $(,)?]) => {
265 $crate::__sg_dynamic_generic_is_shaped!(@string);
266 };
267 ($(#[$attr:meta])* $vis:vis $name:ident, generic String, $doc:literal) => {
268 $crate::__sg_dynamic_generic_is_shaped!(@string);
269 };
270 ($(#[$attr:meta])* $vis:vis $name:ident, generic String, derive: [$($opt:ident),* $(,)?]) => {
271 $crate::__sg_dynamic_generic_is_shaped!(@string);
272 };
273 ($(#[$attr:meta])* $vis:vis $name:ident, generic String) => {
274 $crate::__sg_dynamic_generic_is_shaped!(@string);
275 };
276
277 // ---- doc-string forms, one per shape ----
278 //
279 // These must be matched by literal tokens *before* any arm that opens with
280 // a `$inner:ty` fragment. `macro_rules!` does not backtrack once a `:ty`
281 // fragment has been parsed: a single `$inner:ty, $doc:literal` arm would
282 // consume the type, fail to find the comma, and hard-error with "unexpected
283 // end of macro invocation" instead of falling through to the arms below.
284 ($(#[$attr:meta])* $vis:vis $name:ident, String, $doc:literal, derive: [$($opt:ident),* $(,)?]) => {
285 $crate::dynamic_newtype!($(#[$attr])* #[doc = $doc] $vis $name, String, derive: [$($opt),*]);
286 };
287 ($(#[$attr:meta])* $vis:vis $name:ident, String, $doc:literal) => {
288 $crate::dynamic_newtype!($(#[$attr])* #[doc = $doc] $vis $name, String, derive: []);
289 };
290 ($(#[$attr:meta])* $vis:vis $name:ident, Vec<u8>, $doc:literal, derive: [$($opt:ident),* $(,)?]) => {
291 $crate::dynamic_newtype!($(#[$attr])* #[doc = $doc] $vis $name, Vec<u8>, derive: [$($opt),*]);
292 };
293 ($(#[$attr:meta])* $vis:vis $name:ident, Vec<u8>, $doc:literal) => {
294 $crate::dynamic_newtype!($(#[$attr])* #[doc = $doc] $vis $name, Vec<u8>, derive: []);
295 };
296 ($(#[$attr:meta])* $vis:vis $name:ident, generic $inner:ty, $doc:literal, derive: [$($opt:ident),* $(,)?]) => {
297 $crate::dynamic_newtype!($(#[$attr])* #[doc = $doc] $vis $name, generic $inner, derive: [$($opt),*]);
298 };
299 ($(#[$attr:meta])* $vis:vis $name:ident, generic $inner:ty, $doc:literal) => {
300 $crate::dynamic_newtype!($(#[$attr])* #[doc = $doc] $vis $name, generic $inner, derive: []);
301 };
302
303 // ---- String arm: matched by literal tokens, before the generic arm ----
304 ($(#[$attr:meta])* $vis:vis $name:ident, String) => {
305 $crate::dynamic_newtype!($(#[$attr])* $vis $name, String, derive: []);
306 };
307 ($(#[$attr:meta])* $vis:vis $name:ident, String, derive: [$($opt:ident),* $(,)?]) => {
308 $crate::__sg_newtype_base!(
309 $(#[$attr])* $vis $name($crate::Dynamic<$crate::__private::String>), derive: [$($opt),*]
310 );
311 $crate::__sg_newtype_len!($name);
312 $crate::__sg_dynamic_ctor!(@into $name, $crate::__private::String);
313
314 impl ::core::convert::From<&str> for $name {
315 #[inline]
316 fn from(s: &str) -> Self { Self(<$crate::Dynamic<$crate::__private::String>>::from(s)) }
317 }
318 impl $name {
319 /// Scoped construction into a protected `String` buffer.
320 #[inline]
321 pub fn new_with<F>(f: F) -> Self
322 where F: ::core::ops::FnOnce(&mut $crate::__private::String) {
323 Self(<$crate::Dynamic<$crate::__private::String>>::new_with(f))
324 }
325 }
326 };
327
328 // ---- Vec<u8> arm ----
329 ($(#[$attr:meta])* $vis:vis $name:ident, Vec<u8>) => {
330 $crate::dynamic_newtype!($(#[$attr])* $vis $name, Vec<u8>, derive: []);
331 };
332 ($(#[$attr:meta])* $vis:vis $name:ident, Vec<u8>, derive: [$($opt:ident),* $(,)?]) => {
333 $crate::__sg_newtype_base!(
334 $(#[$attr])* $vis $name($crate::Dynamic<$crate::__private::Vec<u8>>), derive: [$($opt),*]
335 );
336 $crate::__sg_newtype_len!($name);
337 $crate::__sg_dynamic_ctor!(@into $name, $crate::__private::Vec<u8>);
338
339 impl ::core::convert::From<&[u8]> for $name {
340 #[inline]
341 fn from(s: &[u8]) -> Self { Self(<$crate::Dynamic<$crate::__private::Vec<u8>>>::from(s)) }
342 }
343 impl $name {
344 /// Scoped construction into a protected `Vec<u8>` buffer.
345 #[inline]
346 pub fn new_with<F>(f: F) -> Self
347 where F: ::core::ops::FnOnce(&mut $crate::__private::Vec<u8>) {
348 Self(<$crate::Dynamic<$crate::__private::Vec<u8>>>::new_with(f))
349 }
350 }
351
352 $crate::__sg_if_rand! {
353 impl $name {
354 /// Generates `len` random bytes from the system RNG.
355 #[inline]
356 pub fn from_random(len: usize) -> Self { Self(<$crate::Dynamic<$crate::__private::Vec<u8>>>::from_random(len)) }
357
358 /// Generates `len` bytes from a caller-supplied CSPRNG.
359 #[inline]
360 pub fn from_rng<R>(len: usize, rng: &mut R) -> ::core::result::Result<Self, R::Error>
361 where
362 R: $crate::__private::TryRngCore + $crate::__private::TryCryptoRng,
363 {
364 ::core::result::Result::Ok(Self(
365 <$crate::Dynamic<$crate::__private::Vec<u8>>>::from_rng(len, rng)?,
366 ))
367 }
368 }
369 }
370
371 $crate::__sg_if_hex! {
372 impl $name {
373 /// Constant-time hex decode into this secret type.
374 #[inline]
375 pub fn try_from_hex(s: &str) -> ::core::result::Result<Self, $crate::HexError> {
376 ::core::result::Result::Ok(Self(
377 <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_hex(s)?,
378 ))
379 }
380 }
381 impl $crate::ToHex for $name {
382 #[inline]
383 fn to_hex(&self) -> $crate::EncodedSecret {
384 $crate::ToHex::to_hex(&self.0)
385 }
386 #[inline]
387 fn to_hex_upper(&self) -> $crate::EncodedSecret {
388 $crate::ToHex::to_hex_upper(&self.0)
389 }
390 }
391 }
392
393 $crate::__sg_if_base32! {
394 impl $name {
395 /// Constant-time Base32 decode into this secret type.
396 #[inline]
397 pub fn try_from_base32(s: &str) -> ::core::result::Result<Self, $crate::Base32Error> {
398 ::core::result::Result::Ok(Self(
399 <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_base32(s)?,
400 ))
401 }
402 }
403 impl $crate::ToBase32 for $name {
404 #[inline]
405 fn to_base32(&self) -> $crate::EncodedSecret {
406 $crate::ToBase32::to_base32(&self.0)
407 }
408 }
409 }
410
411 $crate::__sg_if_base64! {
412 impl $name {
413 /// Constant-time Base64url decode into this secret type.
414 #[inline]
415 pub fn try_from_base64url(s: &str) -> ::core::result::Result<Self, $crate::Base64Error> {
416 ::core::result::Result::Ok(Self(
417 <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_base64url(s)?,
418 ))
419 }
420 }
421 impl $crate::ToBase64Url for $name {
422 #[inline]
423 fn to_base64url(&self) -> $crate::EncodedSecret {
424 $crate::ToBase64Url::to_base64url(&self.0)
425 }
426 }
427 }
428
429 $crate::__sg_if_bech32! {
430 impl $name {
431 /// HRP-validated Bech32 decode into this secret type.
432 #[inline]
433 pub fn try_from_bech32(s: &str, expected_hrp: &str)
434 -> ::core::result::Result<Self, $crate::Bech32Error> {
435 ::core::result::Result::Ok(Self(
436 <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32(s, expected_hrp)?,
437 ))
438 }
439 /// Bech32 decode without HRP validation.
440 #[inline]
441 pub fn try_from_bech32_unchecked(s: &str)
442 -> ::core::result::Result<Self, $crate::Bech32Error> {
443 ::core::result::Result::Ok(Self(
444 <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32_unchecked(s)?,
445 ))
446 }
447 /// HRP-validated Bech32 decode accepting strings up to `C` characters.
448 #[inline]
449 pub fn try_from_bech32_sized<const C: usize>(s: &str, expected_hrp: &str)
450 -> ::core::result::Result<Self, $crate::Bech32Error> {
451 ::core::result::Result::Ok(Self(
452 <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32_sized::<C>(s, expected_hrp)?,
453 ))
454 }
455 /// Bech32 decode without HRP validation, accepting strings up to `C` characters.
456 #[inline]
457 pub fn try_from_bech32_unchecked_sized<const C: usize>(s: &str)
458 -> ::core::result::Result<Self, $crate::Bech32Error> {
459 ::core::result::Result::Ok(Self(
460 <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32_unchecked_sized::<C>(s)?,
461 ))
462 }
463 }
464 impl $crate::ToBech32 for $name {
465 #[inline]
466 fn try_to_bech32(
467 &self,
468 hrp: &str,
469 case: $crate::Case,
470 ) -> ::core::result::Result<$crate::EncodedSecret, $crate::Bech32Error> {
471 $crate::ToBech32::try_to_bech32(&self.0, hrp, case)
472 }
473 #[inline]
474 fn try_to_bech32_sized<const C: usize>(
475 &self,
476 hrp: &str,
477 case: $crate::Case,
478 ) -> ::core::result::Result<$crate::EncodedSecret, $crate::Bech32Error> {
479 $crate::ToBech32::try_to_bech32_sized::<C>(&self.0, hrp, case)
480 }
481 }
482 }
483
484 $crate::__sg_if_bech32m! {
485 impl $name {
486 /// HRP-validated Bech32m decode into this secret type.
487 #[inline]
488 pub fn try_from_bech32m(s: &str, expected_hrp: &str)
489 -> ::core::result::Result<Self, $crate::Bech32Error> {
490 ::core::result::Result::Ok(Self(
491 <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32m(s, expected_hrp)?,
492 ))
493 }
494 /// Bech32m decode without HRP validation.
495 #[inline]
496 pub fn try_from_bech32m_unchecked(s: &str)
497 -> ::core::result::Result<Self, $crate::Bech32Error> {
498 ::core::result::Result::Ok(Self(
499 <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32m_unchecked(s)?,
500 ))
501 }
502 /// HRP-validated Bech32m decode accepting strings up to `C` characters.
503 #[inline]
504 pub fn try_from_bech32m_sized<const C: usize>(s: &str, expected_hrp: &str)
505 -> ::core::result::Result<Self, $crate::Bech32Error> {
506 ::core::result::Result::Ok(Self(
507 <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32m_sized::<C>(s, expected_hrp)?,
508 ))
509 }
510 /// Bech32m decode without HRP validation, accepting strings up to `C` characters.
511 #[inline]
512 pub fn try_from_bech32m_unchecked_sized<const C: usize>(s: &str)
513 -> ::core::result::Result<Self, $crate::Bech32Error> {
514 ::core::result::Result::Ok(Self(
515 <$crate::Dynamic<$crate::__private::Vec<u8>>>::try_from_bech32m_unchecked_sized::<C>(s)?,
516 ))
517 }
518 }
519 impl $crate::ToBech32m for $name {
520 #[inline]
521 fn try_to_bech32m(
522 &self,
523 hrp: &str,
524 case: $crate::Case,
525 ) -> ::core::result::Result<$crate::EncodedSecret, $crate::Bech32Error> {
526 $crate::ToBech32m::try_to_bech32m(&self.0, hrp, case)
527 }
528 #[inline]
529 fn try_to_bech32m_sized<const C: usize>(
530 &self,
531 hrp: &str,
532 case: $crate::Case,
533 ) -> ::core::result::Result<$crate::EncodedSecret, $crate::Bech32Error> {
534 $crate::ToBech32m::try_to_bech32m_sized::<C>(&self.0, hrp, case)
535 }
536 }
537 }
538
539 $crate::__sg_if_std! {
540 impl ::std::io::Write for $name {
541 #[inline]
542 fn write(&mut self, buf: &[u8]) -> ::std::io::Result<usize> {
543 ::std::io::Write::write(&mut self.0, buf)
544 }
545 #[inline]
546 fn flush(&mut self) -> ::std::io::Result<()> { ::std::io::Write::flush(&mut self.0) }
547 }
548 impl $name {
549 /// Zeroizing reader over the secret bytes.
550 #[inline]
551 pub fn as_reader(&self) -> $crate::DynamicReader<'_> { self.0.as_reader() }
552 }
553 }
554 };
555
556 // ---- explicit generic arm: caller opts in to the reduced API ----
557 //
558 // `String` and `Vec<u8>` above are matched as literal tokens, so a type
559 // alias (`type MyStr = String`) does not reach them. Rather than let such
560 // an input fall through and silently lose the shaped API, the fallback
561 // requires the `generic` marker; anything else hits the catch-all below.
562 ($(#[$attr:meta])* $vis:vis $name:ident, generic $inner:ty) => {
563 $crate::dynamic_newtype!($(#[$attr])* $vis $name, generic $inner, derive: []);
564 };
565 ($(#[$attr:meta])* $vis:vis $name:ident, generic $inner:ty, derive: [$($opt:ident),* $(,)?]) => {
566 $crate::__sg_newtype_base!(
567 $(#[$attr])* $vis $name($crate::Dynamic<$inner>), derive: [$($opt),*]
568 );
569 $crate::__sg_dynamic_ctor!($name, $inner);
570 };
571
572 // ---- catch-all: unrecognised inner type ----
573 //
574 // Uses `$($rest:tt)+` rather than `$inner:ty` so it can never itself
575 // hard-error on a malformed tail; the message names what was written.
576 ($(#[$attr:meta])* $vis:vis $name:ident, $($rest:tt)+) => {
577 ::core::compile_error!(::core::concat!(
578 "dynamic_newtype!: `",
579 ::core::stringify!($($rest)+),
580 "` is not one of the shaped inner types. `String` and `Vec<u8>` are \
581 matched as literal tokens, so a type alias (e.g. `type MyStr = String`) \
582 or a fully-qualified path (e.g. `alloc::vec::Vec<u8>`) does NOT match \
583 them. If what you wrote IS a `String` or a `Vec<u8>` under another \
584 spelling, write the literal token to get the full API for that shape -- \
585 do NOT reach for `generic` to silence this, because the macro cannot \
586 see through a path or an alias either, and you would land on the \
587 reduced arm with the growable payload and none of the methods that \
588 handle it safely. `generic <type>` is for an inner type that is \
589 genuinely neither `String` nor `Vec<u8>`; it gives RevealSecret, \
590 RevealSecretMut, Debug, Zeroize, and `new`."
591 ));
592 };
593}
594
595/// Rejects `generic` applied to a shape that has its own arm.
596///
597/// One helper rather than the message inline in eight arms, so the wording
598/// cannot drift between the doc-string and `derive:` tails.
599#[doc(hidden)]
600#[macro_export]
601macro_rules! __sg_dynamic_generic_is_shaped {
602 (@bytes) => {
603 ::core::compile_error!(
604 "dynamic_newtype!: write `Vec<u8>`, not `generic Vec<u8>`. The `generic` \
605 arm emits only the shape-independent surface, so this spelling withholds \
606 `SecretLen`, `From<&[u8]>`, `new_with`, the encoders, `from_random`, and \
607 -- under `std` -- the `std::io::Write` impl, which is the one growth path \
608 that wipes the buffer it abandons. It is the same growable payload with \
609 strictly less API and no safe way to grow it. `generic` is for inner \
610 types that are neither `String` nor `Vec<u8>`."
611 );
612 };
613 (@string) => {
614 ::core::compile_error!(
615 "dynamic_newtype!: write `String`, not `generic String`. The `generic` arm \
616 emits only the shape-independent surface, so this spelling withholds \
617 `SecretLen`, `From<&str>` and `new_with` -- the in-place constructor that \
618 builds the secret inside the protected buffer rather than in an \
619 unprotected one first. It is the same growable payload with strictly \
620 less API. `generic` is for inner types that are neither `String` nor \
621 `Vec<u8>`."
622 );
623 };
624}
625
626#[doc(hidden)]
627#[macro_export]
628macro_rules! __sg_dynamic_ctor {
629 // Shaped arms: accept anything convertible to the inner value, so
630 // `Name::new("literal")` and `Name::new(string)` both work — the shape a
631 // hand-written `new(impl Into<String>)` has, so call sites need not move.
632 (@into $name:ident, $inner:ty) => {
633 impl $name {
634 /// Creates the secret from any value convertible into the inner type.
635 #[inline(always)]
636 pub fn new(value: impl ::core::convert::Into<$inner>) -> Self {
637 Self(<$crate::Dynamic<$inner>>::new(value.into()))
638 }
639 }
640 };
641 ($name:ident, $inner:ty) => {
642 impl $name {
643 /// Moves a value onto the heap as this secret type.
644 #[inline(always)]
645 pub fn new<U>(value: U) -> Self
646 where
647 U: ::core::convert::Into<$crate::__private::Box<$inner>>,
648 {
649 Self(<$crate::Dynamic<$inner>>::new(value))
650 }
651 }
652 };
653}