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