Skip to main content

SecretVo

Derive Macro SecretVo 

Source
#[derive(SecretVo)]
{
    // Attributes available to this derive:
    #[vo]
}
Expand description

Derives SecretVo for a single-field tuple struct, with a Debug impl that always redacts the wrapped value.

Unlike ValueObject, this derive generates its own Debug impl (printing TypeName(***), never the wrapped value) - so the target must not derive or implement Debug itself, or the two impls collide (E0119). It never generates Display either, which is why no_display is rejected here: there is nothing for it to suppress.

See derive_value_object for the shared single-field-tuple-struct requirement and the validate attribute, which behaves identically here. rename_all is rejected for the same reason as on ValueObject.

§Attributes

  • validate = "path::to::fn" - see derive_value_object.
  • zeroize - generates a Drop impl that calls Zeroize::zeroize on the wrapped value when it goes out of scope. Requires this crate’s own zeroize Cargo feature to be enabled, and the calling crate to depend on the zeroize crate directly (the generated Drop impl references it by a bare path, which only resolves at the call site). Without the feature enabled, #[vo(zeroize)] is rejected at compile time with a message naming both requirements, rather than failing later with a confusing unresolved-crate error.

Unlike ValueObject/EntityId/ EnumVo, this derive never generates serde support, even with this crate’s serde Cargo feature enabled: a Serialize impl on a secret risks writing the raw value to a log or response body by accident, which is exactly what this type exists to prevent. Wrap a SecretVo in your own type with an explicit, intentional Serialize impl if you truly need to send it somewhere.

§Examples

use ddd_toolkit_core::domain::SecretVo;
use ddd_toolkit_macro::SecretVo as DeriveSecretVo;

#[derive(Clone, PartialEq, DeriveSecretVo)]
struct ApiKey(String);

let secret = ApiKey::try_new("sekret".to_string())?;
assert_eq!(secret.expose_secret(), "sekret");

let debug = format!("{secret:?}");
assert_eq!(debug, "ApiKey(***)");
assert!(!debug.contains("sekret"));

With the zeroize feature enabled on this crate, and zeroize in the caller’s own dependencies:

ⓘ
use ddd_toolkit_core::domain::SecretVo;
use ddd_toolkit_macro::SecretVo as DeriveSecretVo;

#[derive(Clone, PartialEq, DeriveSecretVo)]
#[vo(zeroize)]
struct ZeroizingSecret([u8; 4]);

let secret = ZeroizingSecret::try_new([1, 2, 3, 4])?;
drop(secret); // wipes the inner bytes on the way out