1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
//! Derive macros for `ddd-toolkit-core`'s value-object-shaped traits:
//! [`ValueObject`](derive_value_object), [`EntityId`](derive_entity_id),
//! [`SecretVo`](derive_secret_vo), and [`EnumVo`](derive_enum_vo). Works
//! alongside `ddd-toolkit-core` directly, or through the `ddd-toolkit`
//! facade crate - the generated code resolves the right crate path either
//! way, so it doesn't matter which one the caller's `Cargo.toml` names.
//!
//! Three of the four derives ([`ValueObject`](derive_value_object),
//! [`EntityId`](derive_entity_id), [`SecretVo`](derive_secret_vo)) apply to
//! a tuple struct with exactly one non-`pub` field, e.g. `struct
//! Email(String);`. The fourth, [`EnumVo`](derive_enum_vo), applies to a
//! fieldless enum instead. All four accept `#[vo(...)]` attributes to
//! configure the generated code; each derive's own docs list which
//! attributes it accepts and why the others are rejected.
//!
//! # Example
//!
//! ```
//! use ddd_toolkit_core::domain::{ValidationError, Wrapped};
//! use ddd_toolkit_macro::ValueObject;
//!
//! #[derive(ValueObject, Clone, PartialEq, Debug)]
//! #[vo(validate = "validate_email")]
//! struct Email(String);
//!
//! fn validate_email(s: &str) -> Result<(), ValidationError> {
//! if s.contains('@') {
//! Ok(())
//! } else {
//! Err(ValidationError::new("Email", "missing @"))
//! }
//! }
//!
//! let email = Email::try_from("a@b.com".to_string())?;
//! assert_eq!(email.as_inner(), "a@b.com");
//! assert!(Email::try_from("no-at-sign".to_string()).is_err());
//! # Ok::<(), ValidationError>(())
//! ```
use crateexpand_enum_vo;
use crateexpand_value_object;
use crate::;
use TokenStream;
use ;
/// Derives [`ValueObject`], [`Wrapped`], `TryFrom<Inner>`, `From<Self> for
/// Inner`, and `Display` for a single-field tuple struct.
///
/// [`ValueObject`]: https://docs.rs/ddd-toolkit-core/latest/ddd_toolkit_core/domain/trait.ValueObject.html
/// [`Wrapped`]: https://docs.rs/ddd-toolkit-core/latest/ddd_toolkit_core/domain/trait.Wrapped.html
///
/// # Requirements
///
/// The target must be a tuple struct with exactly one field, and that
/// field must not be `pub`. A `pub` field would let callers construct the
/// type directly (`Email("anything".into())`), bypassing the validation
/// this derive puts into the generated `TryFrom` impl - so both violations
/// are rejected at compile time rather than left as a foot-gun.
///
/// # Attributes
///
/// All are passed as `#[vo(...)]` on the struct itself:
///
/// - `validate = "path::to::fn"` - runs `fn(&Inner) -> Result<(),
/// ValidationError>` inside the generated `TryFrom` before the value is
/// constructed. Without it, `TryFrom::Error` is
/// [`Infallible`](std::convert::Infallible).
/// - `no_display` - skips the generated `Display` impl, so a hand-written
/// one can be provided instead.
///
/// `rename_all` and `zeroize` are rejected here: `rename_all` only affects
/// [`EnumVo`](derive_enum_vo)'s variant names, and `zeroize` only makes
/// sense on [`SecretVo`](derive_secret_vo), which owns a value worth
/// scrubbing from memory on drop.
///
/// # `serde`
///
/// With this crate's own `serde` Cargo feature enabled (and the calling
/// crate depending on `serde` directly - proc-macro output resolves bare
/// paths at the call site, not here), this also derives `Serialize`
/// (delegating to the inner value) and `Deserialize` (deserializing the
/// inner value, then re-running it through the same validating `TryFrom`
/// this derive generates, so an invalid value is rejected during
/// deserialization exactly as it would be through `TryFrom` directly).
///
/// # Examples
///
/// ```
/// use ddd_toolkit_core::domain::{ValidationError, Wrapped};
/// use ddd_toolkit_macro::ValueObject;
///
/// #[derive(ValueObject, Clone, PartialEq, Debug)]
/// #[vo(validate = "validate_email")]
/// struct Email(String);
///
/// fn validate_email(s: &str) -> Result<(), ValidationError> {
/// if s.contains('@') {
/// Ok(())
/// } else {
/// Err(ValidationError::new("Email", "missing @"))
/// }
/// }
///
/// let email = Email::try_from("a@b.com".to_string())?;
/// assert_eq!(email.as_inner(), "a@b.com");
/// assert_eq!(email.to_string(), "a@b.com");
/// assert!(Email::try_from("no-at-sign".to_string()).is_err());
/// # Ok::<(), ValidationError>(())
/// ```
/// Derives [`EntityId`] - and everything
/// [`#[derive(ValueObject)]`](derive_value_object) derives - for a
/// single-field tuple struct.
///
/// [`EntityId`]: https://docs.rs/ddd-toolkit-core/latest/ddd_toolkit_core/domain/trait.EntityId.html
///
/// On top of the `ValueObject` bundle, this adds `Eq`, `Hash`, `Ord`, and
/// `PartialOrd`, all delegating to the wrapped field - two ids compare and
/// hash equal exactly when their inner values do. That makes the result
/// usable as a `HashMap`/`HashSet` key or in a sorted collection, which is
/// the common case for an identifier type.
///
/// See [`derive_value_object`] for the shared requirements (single-field
/// tuple struct, non-`pub` field) and `#[vo(...)]` attributes (`validate`,
/// `no_display`) - both apply here unchanged, and `rename_all`/`zeroize`
/// are rejected for the same reasons. `serde` support (see
/// [`derive_value_object`]'s docs) applies here unchanged too.
///
/// # Examples
///
/// ```
/// use ddd_toolkit_core::domain::Wrapped;
/// use ddd_toolkit_macro::EntityId;
/// use std::collections::HashSet;
///
/// #[derive(EntityId, Clone, PartialEq, Debug)]
/// struct UserId(u64);
///
/// let a = UserId::try_from(1)?;
/// let b = UserId::try_from(2)?;
/// assert!(a < b);
///
/// let ids: HashSet<UserId> = HashSet::from([UserId::try_from(1)?, UserId::try_from(1)?]);
/// assert_eq!(ids.len(), 1, "equal inner values dedupe to one id");
/// # Ok::<(), std::convert::Infallible>(())
/// ```
/// Derives [`SecretVo`] for a single-field tuple struct, with a `Debug`
/// impl that always redacts the wrapped value.
///
/// [`SecretVo`]: https://docs.rs/ddd-toolkit-core/latest/ddd_toolkit_core/domain/trait.SecretVo.html
///
/// Unlike [`ValueObject`](derive_value_object), 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`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html#tymethod.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`](derive_value_object)/[`EntityId`](derive_entity_id)/
/// [`EnumVo`](derive_enum_vo), 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"));
/// # Ok::<(), std::convert::Infallible>(())
/// ```
///
/// With the `zeroize` feature enabled on this crate, and `zeroize` in the
/// caller's own dependencies:
///
/// ```ignore
/// 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
/// # Ok::<(), std::convert::Infallible>(())
/// ```
/// Derives [`EnumVo`], `Display`, and `FromStr` for a fieldless enum.
///
/// [`EnumVo`]: https://docs.rs/ddd-toolkit-core/latest/ddd_toolkit_core/domain/trait.EnumVo.html
///
/// `EnumVo::variants()`, `Display`, and `FromStr` are all generated from
/// the same variant list, so they can't drift out of sync with each other
/// or with the enum's actual variants. Every variant must be a unit
/// variant (no fields) - a variant carrying data isn't a simple
/// enumeration, so the derive rejects it at compile time instead of
/// silently ignoring the payload.
///
/// `validate` is rejected here: `FromStr` already exhaustively checks
/// variant membership, so there is no "invalid but parseable" value left
/// for a validator to catch. `zeroize` is rejected for the same reason as
/// on [`ValueObject`](derive_value_object) - a fieldless enum owns nothing
/// worth scrubbing.
///
/// # Attributes
///
/// - `no_display` - see [`derive_value_object`].
/// - `rename_all = "..."` - renames every variant for `Display`/`FromStr`
/// according to one of: `PascalCase` (the default - unchanged), `snake_case`,
/// `SCREAMING_SNAKE_CASE`, `kebab-case`, `lowercase`, `UPPERCASE`, or
/// `camelCase`. An unrecognized rule name is rejected at compile time,
/// listing the valid options.
///
/// With this crate's own `serde` Cargo feature enabled (and the calling
/// crate depending on `serde` directly), this also derives `Serialize`
/// and `Deserialize`, both using the same variant/string table
/// `Display`/`FromStr` are generated from - so serializing writes the
/// `Display` string and deserializing accepts exactly what `FromStr`
/// accepts, `rename_all` included.
///
/// # Examples
///
/// ```
/// use ddd_toolkit_core::domain::EnumVo;
/// use ddd_toolkit_macro::EnumVo as DeriveEnumVo;
/// use std::str::FromStr;
///
/// #[derive(Debug, Clone, Copy, PartialEq, DeriveEnumVo)]
/// enum Status {
/// Active,
/// Inactive,
/// }
///
/// assert_eq!(Status::variants(), &[Status::Active, Status::Inactive]);
/// assert_eq!(Status::Active.to_string(), "Active");
/// assert_eq!(Status::from_str("Active"), Ok(Status::Active));
/// assert!(Status::from_str("bogus").is_err());
/// ```
///
/// With `rename_all`, `Display` and `FromStr` agree on the renamed form:
///
/// ```
/// use ddd_toolkit_macro::EnumVo as DeriveEnumVo;
/// use std::str::FromStr;
///
/// #[derive(Debug, Clone, Copy, PartialEq, DeriveEnumVo)]
/// #[vo(rename_all = "snake_case")]
/// enum HttpMethod {
/// Get,
/// PostRequest,
/// }
///
/// assert_eq!(HttpMethod::PostRequest.to_string(), "post_request");
/// assert_eq!(HttpMethod::from_str("post_request"), Ok(HttpMethod::PostRequest));
/// assert!(HttpMethod::from_str("PostRequest").is_err());
/// ```