Skip to content

feat: add typed upcaster API#34

Merged
patrickleet merged 2 commits into
mainfrom
feat/typed-upcasters
May 21, 2026
Merged

feat: add typed upcaster API#34
patrickleet merged 2 commits into
mainfrom
feat/typed-upcasters

Conversation

@patrickleet
Copy link
Copy Markdown
Collaborator

@patrickleet patrickleet commented May 21, 2026

Summary

  • replace public upcaster examples with typed OldPayload => NewPayload transforms
  • generate macro wrapper functions that decode, transform, and re-encode payloads through crate codec helpers
  • propagate upcaster payload decode/encode failures through hydrate and snapshot hydrate

Verification

  • cargo fmt --check
  • git diff --check
  • cargo test --all-features

Implements [[tasks/simplify-upcaster-api]]

Summary by CodeRabbit

  • New Features

    • Event upcasting now uses typed payload transforms with explicit source→target mappings and supports chaining; upcasting failures propagate as descriptive errors.
  • Documentation

    • README updated with examples for the new typed upcaster syntax and direct upcasting usage.
  • Tests

    • Test suite refactored to exercise typed payload transforms, chaining, and error propagation during hydration and snapshot replay.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e93888b4-4523-4936-b7b3-98ea68fbcae5

📥 Commits

Reviewing files that changed from the base of the PR and between 909321d and ff3312a.

📒 Files selected for processing (4)
  • README.md
  • sourced_rust_macros/src/lib.rs
  • src/entity/upcaster.rs
  • tests/sourced_upcasting/aggregate.rs
✅ Files skipped from review due to trivial changes (1)
  • README.md

📝 Walkthrough

Walkthrough

The PR refactors event upcasting from raw byte transforms to typed payload transforms: EventUpcaster.transform now takes &EventRecord and returns Result; adds upcast_payload for typed decode/transform/encode; macros generate typed wrapper upcasters; tests and README examples updated to the new SourceType => TargetType syntax and chaining.

Changes

Typed Event Upcasting

Layer / File(s) Summary
Typed upcaster contract and helper
src/entity/upcaster.rs
EventUpcaster.transform changed to fn(&EventRecord) -> Result<Vec<u8>, UpcastError>; added UpcastError::PayloadTransform; added pub fn upcast_payload<From, To>(...) for typed decode/transform/encode with error mapping.
Upcasting pipeline integration & tests
src/entity/upcaster.rs
upcast_one updated to invoke new transform signature and propagate failures; test helpers and upcaster tests rewritten to use typed payload encoding/decoding and passthrough helper.
Macro parsing & wrapper generation
sourced_rust_macros/src/lib.rs
UpcasterDef extended with source_type/target_type; aggregate! and #[sourced] parsing accept SourceType => TargetType; new helpers generate per-upcaster wrapper methods and an upcasters() static slice, injected into macro output.
Public re-exports
src/entity/mod.rs, src/lib.rs
upcast_payload re-exported from entity and included at the crate root.
Aggregate test updates (upcasting)
tests/upcasting/aggregate.rs, tests/sourced_upcasting/aggregate.rs
Typed tuple aliases InitializedV1/V2/V3 added; upcasters refactored from byte/bitcode to typed tuple transforms; TodoV2/TodoV3 registrations updated to typed mapping syntax and explicit v1→v2→v3 chaining.
Integration tests & error propagation
tests/upcasting/main.rs
identity_payload adapted to new transform signature; tests use TodoV2::upcasters() and add assertions that hydrate/hydrate_from_snapshot return RepositoryError::Replay with "failed to upcast event Initialized" when typed upcaster decoding fails.
Documentation examples
README.md
Examples and guidance updated to typed payload aliases and SourceType => TargetType registration; “Direct Upcasting” section replaced to show upcast_events(events, Todo::upcasters()).

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • patrickleet/sourced_rust#15: Introduces earlier upcaster/codec changes that are closely related to this PR's typed-transform upcasting work.

Poem

🐰 I nibble bytes and sew them tight,

tuples bloom from V1 to V3,
Macros wrap the path just right,
upcast_payload sets the key,
Hops of code make schemas free.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add typed upcaster API' directly and accurately describes the main change: introducing a new typed upcaster API that replaces byte-based transforms with typed payload transforms throughout the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/typed-upcasters

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/entity/upcaster.rs (1)

35-40: ⚡ Quick win

Expose the inner payload error via Error::source().

Line 81 implements std::error::Error without overriding source(), so the EventRecordError captured in PayloadTransform is not visible through the standard error chain.

Suggested fix
-impl std::error::Error for UpcastError {}
+impl std::error::Error for UpcastError {
+    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+        match self {
+            UpcastError::PayloadTransform { source, .. } => Some(source),
+            _ => None,
+        }
+    }
+}

Also applies to: 81-81

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/entity/upcaster.rs` around lines 35 - 40, The Error enum's
std::error::Error impl should expose the inner EventRecordError for the
PayloadTransform variant via source(); update the impl of std::error::Error for
Error to match other variants and return Some(&self.source) (as &dyn
std::error::Error) when the variant is PayloadTransform (the variant named
PayloadTransform holding source: EventRecordError), and return None for variants
that have no inner error so the standard error chain reveals the
EventRecordError.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Line 1379: The example call to upcast_events(events, Todo::upcasters())
ignores that upcast_events can fail; update the snippet to surface that
fallibility by handling the Result returned (e.g. propagate with ? from a
fallible function or match on Ok/Err and log/return the error). Specifically
change the use of upcast_events and the upcasted binding so errors from
upcast_events (and the Todo::upcasters() pipeline) are either returned from the
enclosing function or handled explicitly (e.g., log/convert the error), ensuring
the example compiles when copy/pasted.

In `@sourced_rust_macros/src/lib.rs`:
- Around line 204-223: generate_upcaster_tokens currently emits wrapper
functions at module scope (the wrapper iterator creating fn `#wrapper`(...) that
calls `#transform_fn`) but `#transform_fn` can be a syn::Path like Self::migrate_*
which is invalid outside an impl; move the generated wrappers inside an impl
block for the owner (wrap the wrapper_defs in impl `#owner` { ... } or emit
methods instead) so that Self::... resolves correctly, ensuring wrapper_defs
creation still references the same symbols (wrapper, transform_fn, to_version,
source_type, target_type) but are declared as impl `#owner` :: `#wrapper` or methods
inside impl `#owner` rather than free functions.

---

Nitpick comments:
In `@src/entity/upcaster.rs`:
- Around line 35-40: The Error enum's std::error::Error impl should expose the
inner EventRecordError for the PayloadTransform variant via source(); update the
impl of std::error::Error for Error to match other variants and return
Some(&self.source) (as &dyn std::error::Error) when the variant is
PayloadTransform (the variant named PayloadTransform holding source:
EventRecordError), and return None for variants that have no inner error so the
standard error chain reveals the EventRecordError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b686514-7f8a-41e6-8768-5bd8cc6fb761

📥 Commits

Reviewing files that changed from the base of the PR and between 12cc549 and 909321d.

📒 Files selected for processing (8)
  • README.md
  • sourced_rust_macros/src/lib.rs
  • src/entity/mod.rs
  • src/entity/upcaster.rs
  • src/lib.rs
  • tests/sourced_upcasting/aggregate.rs
  • tests/upcasting/aggregate.rs
  • tests/upcasting/main.rs

Comment thread README.md Outdated
Comment thread sourced_rust_macros/src/lib.rs
@patrickleet
Copy link
Copy Markdown
Collaborator Author

patrickleet commented May 21, 2026

Addressed the remaining CodeRabbit nitpick in ff3312a as well: UpcastError now implements Error::source() for PayloadTransform so the wrapped EventRecordError is available through the standard error chain. Added unit coverage for that path.

Validation run locally:

  • cargo fmt --check
  • git diff --check
  • cargo test --test sourced_upcasting
  • cargo test --test upcasting
  • cargo test entity::upcaster
  • cargo test --all-features

@patrickleet patrickleet merged commit 67b4099 into main May 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant