refactor: harden distributed_macros expansion and diagnostics#82
Conversation
Restructure the four proc-macros in distributed_macros/src/lib.rs (sourced, aggregate!, digest, enqueue) to the testable `expand_* -> syn::Result<TokenStream2>` shape already used by snapshot.rs and read_model.rs. The thin `#[proc_macro*]` entry points now convert errors via `.unwrap_or_else(|e| e.to_compile_error())`. Generated output is byte-identical — only the structure and error plumbing changed (verified: all 250+ macro-using tests in the main crate still pass). Diagnostics: unknown keyword args in parse_digest_args, parse_sourced_args, parse_event_args, and parse_enqueue_args now produce pointed, spanned syn::Error messages that name the bad key and list the valid ones, instead of being silently left unconsumed and surfacing as a bare "unexpected token". Also added up-front checks for duplicate #[event] names and #[event] methods missing a self receiver, and a clearer "missing entity field" message for bare #[sourced()]. enqueue fix: #[enqueue] now accepts `entity = field` so a renamed entity field produces a correct `is_replaying()` guard instead of a confusing "no field `entity`" error pointing at the user's method. ReplayError: kept as `String`. Replay errors are flattened from heterogeneous sources (per-event decode errors, user method errors of arbitrary E, unknown-event messages) via `e.to_string()`; a typed error would have to be generic over each method's error type or erase them anyway. Rationale documented inline. Tests: added unit tests for the new expand_*/parse_* functions and a trybuild compile-fail suite (tests/compile_fail/*.rs + harness) covering an unsupported #[event] signature, duplicate event names, unknown attribute keys, #[sourced] missing the entity field, and the renamed-entity #[enqueue] footgun. Implements [[tasks/macro-crate-hardening]]
|
Warning Review limit reached
More reviews will be available in 33 minutes and 33 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (13)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary
Hardens the
distributed_macrosproc-macro crate to make expansion testable and misuse produce pointed, spanned diagnostics. Brings the four macros inlib.rs(sourced,aggregate!,digest,enqueue) up to the pattern already used bysnapshot.rs/read_model.rs.Generated output is unchanged. The refactor only changes how the code is structured and how errors are produced — not what the macros emit. This is verified by the full main-crate test suite (250+ tests, including
tests/sourced*,tests/enqueue,tests/todos,tests/upcasting,tests/snapshots) all passing unmodified.Changes
Testable shape — each macro now has an
expand_* -> syn::Result<TokenStream2>core, with a thin#[proc_macro*]entry point that calls it and converts errors via.unwrap_or_else(|e| e.to_compile_error()). This mechanical change enables unit testing of expansion.Explicit unknown-key diagnostics —
parse_digest_args,parse_sourced_args,parse_event_args, andparse_enqueue_argspreviously left unknown keyword args silently unconsumed (surfacing later as a bare "unexpected token"). They now reject unknown keys with a spannedsyn::Errornaming the bad key and listing the valid ones, e.g.:unsupported keyversoinin #[digest(...)]; expectedwhenorversion``Also added up-front checks for duplicate
#[event]names, `#[event]` methods missing a `self` receiver, and a clear message for bare `#[sourced()]` with no entity field.#[enqueue]renamed-entity fix — the replay guard was hardcoded toself.entity.is_replaying(). A renamed entity field produced a confusing "no fieldentity" error pointing at the user's method.#[enqueue]now acceptsentity = field(defaulting toentity), matching how the other macros configure the entity field.trybuild compile-fail suite — new
distributed_macros/tests/with atrybuilddev-dependency and five fixtures covering: unsupported#[event]signature, duplicate event names, unknown attribute keys,#[sourced]missing the entity field, and the renamed-entity#[enqueue]footgun. Plus unit tests for the newexpand_*/parse_*functions.ReplayErrordecision — kept asString. Replay errors are flattened from heterogeneous sources (per-eventdecode()errors, user method errors of arbitraryE, unknown-event messages) viae.to_string(). A typed error would have to be generic over each method's error type or erase them anyway, soStringis the smaller, honest representation. Rationale documented inline at bothimpl Aggregateblocks.Note on dev-dependency cycle
trybuild fixtures need the
distributedruntime crate to type-check generateddistributed::...code, sodistributed_macrosnow dev-depends ondistributed(which depends ondistributed_macros). Cargo permits dev-dependency cycles; this does not affect the normal dependency graph.Testing
cargo fmt --all— cleancargo clippy -p distributed_macros— cleancargo test -p distributed_macros— 46 unit tests + 1 trybuild harness (5 fixtures) pass..stderrsnapshots generated withTRYBUILD=overwriteand reviewed (all pointed/spanned).cargo build(default features) — cleancargo test(default features) — entire workspace passes, including all macro-using integration tests with no changes to their expected behavior, confirming generated output is unchanged.🤖 Generated with Claude Code