ddd-toolkit-macro
Derive macros for ddd-toolkit-core:
value objects, identity-based entities, domain events, and aggregate event queues.
The macros generate trait implementations and accessors; they do not provide
persistence, event delivery, or synchronization.
Installation
Your crate must depend directly on ddd-toolkit-core: generated code references
its traits. Renamed core dependencies are supported, but depending only on the
ddd-toolkit facade is not sufficient for crate-path resolution.
Derives
| Derive | Supported input | Generated API |
|---|---|---|
ValueObject |
Nonempty named or tuple structs, nonempty enums | Core ValueObject, Debug, Clone, PartialEq, Eq, Hash, constructors, and accessors |
Entity |
Named struct with exactly one #[entity(id)] field |
Core Entity, inherent id(), and identity-based PartialEq, Eq, Hash |
DomainEvent |
Struct or enum | Core DomainEvent marker |
AggregateRoot |
Named struct implementing core Entity, with exactly one #[aggregate(events)] field |
Core AggregateRoot with record_event and pull_events |
Do not separately implement or derive traits already generated by a macro. Field types must satisfy the corresponding generated trait bounds.
Value objects
For a single-field struct, Inner is the field type; as_inner() borrows that
field and into_inner() returns it by value. For multi-field structs and enums,
Inner is Self, so those methods borrow or return the whole value.
Structs receive new(...) with arguments in field declaration order.
Multi-field named structs also receive borrowed accessors named after their
fields; multi-field tuple structs receive field_0(), field_1(), and so on.
use ValueObject;
;
Enums receive new_<variant>, is_<variant>, and as_<variant> methods.
The suffix is the fully lowercased variant name, not snake case: InProgress
becomes inprogress. A variant accessor returns None for another variant,
Some(()) for a unit variant, or a tuple of field references for a data variant,
even when it has only one field. Lowercased names must be distinct, and generated
method names must not collide with other methods or field accessors.
Validation
Specify validate and error together. The validator is a Rust path, not a
quoted string. It receives &Inner for a single-field struct or &Self for a
multi-field struct or enum, and returns Result<(), ErrorType>.
use ValueObject;
;
Validation replaces new with try_new (or new_<variant> with
try_new_<variant>). Constructor errors are propagated unchanged; validator
panics are not caught.
Validated struct fields cannot be unrestricted pub. This does not prevent all
bypasses: direct construction within the defining module, restricted field
visibility, and direct enum variant construction can bypass validation.
Serialization
Enable serde support on the core dependency:
Then opt in per value object:
use ValueObject;
;
#[value_object(serde)] generates Serialize and Deserialize through the core
crate's serde re-export; it is not a feature on ddd-toolkit-macro. Add serde or
a format crate directly to your dependencies if your own code uses their APIs.
The attribute can be combined with validate and error in the same
#[value_object(...)] declaration.
Single-field structs serialize as their inner value, multi-field named structs
as structs, multi-field tuple structs as tuples, and enums as externally tagged
variants. Tuple variants retain tuple serialization even with one field.
Deserialization runs the configured validator; validation errors become
value object validation failed rather than preserving the original error text.
Entities and aggregate events
Entity compares and hashes only the field marked #[entity(id)]; other fields
do not participate. It provides an inherent borrowed id() accessor, but no
constructor or identifier validation.
DomainEvent is a marker implementation for a struct or enum. It does not add
serialization, methods, or Send/Sync bounds.
use AggregateRoot as _;
use ;
The event queue must be spelled Vec<Event> or std::vec::Vec<Event>; aliases
and other vector paths are not accepted. Its event type must implement core
DomainEvent.
Import the core AggregateRoot trait to call record_event and pull_events:
they are trait methods, not inherent methods. Recording appends to the queue in
insertion order and may allocate. Pulling moves out the queued events and leaves
an empty vector. Both methods require mutable access; neither publishes events,
persists them, or updates domain state.
Attributes
| Attribute | Placement | Effect |
|---|---|---|
#[value_object(validate = path, error = ErrorType)] |
ValueObject struct or enum |
Generates fallible, validated constructors |
#[value_object(serde)] |
ValueObject struct or enum |
Generates serde serialization and deserialization; requires core's serde feature |
#[entity(id)] |
One named struct field | Selects the entity identifier |
#[aggregate(events)] |
One named struct field | Selects the aggregate's pending event vector |
See the API documentation for further details and executable examples.
License
Licensed under either of Apache License, Version 2.0 or MIT license at your option.