feat: async microsvc transports#43
Conversation
Implements the first slice of [[specs/async-microsvc-transports]]. Adds handler metadata, message envelopes, subscription planning, and projection handler envelope dispatch.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughService registration and dispatch were refactored around a transport-visible Message and HandlerSpec model, register_handlers! macro updated to a service-first API, Context now stores Message, and projection services/tests were simplified by removing custom projection wrappers. ChangesHandler Specification Framework Migration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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 `@src/microsvc/service.rs`:
- Around line 208-209: The handlers HashMap is keyed only by name so commands
and events with the same name collide; change the registry key to include
MessageKind (e.g., use (MessageKind, String) as the key) and update all places
that access it: registration logic that inserts into handlers, lookup in
dispatch(), the handles() method, and any code used by subscription_plan() and
subscribe() to use (kind, name) instead of name; also ensure registration either
rejects duplicate (kind,name) entries or overwrites intentionally so HandlerSpec
and handlers remain consistent.
- Around line 693-695: message_to_session currently copies metadata keys as-is
which causes transport-dependent lookup (e.g., X-Hasura-* vs lowercase); update
message_to_session to normalize keys (e.g., lowercase) before building the vars
map so Session::from_map receives normalized keys; locate the function
message_to_session and the local vars HashMap<String,String> built from
message.metadata and transform each key to lowercase (or whatever canonical form
used by src/microsvc/http.rs) before calling Session::from_map.
In `@tests/sagas/handlers/saga/on_inventory_reserved.rs`:
- Around line 4-5: The handler SPEC is incorrectly declared as a command; since
it consumes the InventoryReserved event it should use an event spec. Update the
SPEC declaration (pub const SPEC) to call
sourced_rust::microsvc::HandlerSpec::event(...) instead of
HandlerSpec::command(...), passing the InventoryReserved event identifier used
in this test (replace COMMAND with the appropriate InventoryReserved event
constant/name).
In `@tests/sagas/handlers/saga/on_order_completed.rs`:
- Around line 4-5: The handler is incorrectly registered as a command; update
the SPEC to register it as an event handler by replacing
HandlerSpec::command(COMMAND) with HandlerSpec::event(COMMAND) (and optionally
rename the COMMAND constant to something like EVENT or ORDER_COMPLETED_EVENT for
clarity), ensuring the constant references the OrderCompleted completion event.
In `@tests/sagas/handlers/saga/on_order_created.rs`:
- Around line 4-5: The handler SPEC is currently registered as a command which
breaks event subscription; change the spec to register it as an event consumer
by replacing sourced_rust::microsvc::HandlerSpec::command(COMMAND) with the
event-style spec (e.g., sourced_rust::microsvc::HandlerSpec::event(COMMAND) or
the library's consumer variant) so the on_order_created saga (SPEC / COMMAND) is
treated as an event consumer.
In `@tests/sagas/handlers/saga/on_payment_succeeded.rs`:
- Around line 4-5: SPEC is currently defined as
sourced_rust::microsvc::HandlerSpec::command(COMMAND) but PaymentSucceeded is an
upstream event; change the spec to use HandlerSpec::event(...) instead of
.command(...) so the saga subscribes as an event listener. Update the line
declaring SPEC to call sourced_rust::microsvc::HandlerSpec::event(COMMAND)
(ensuring COMMAND still refers to the PaymentSucceeded message) so subscription
planning treats it as an event.
🪄 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: 41012709-0231-47de-a028-d33f0e92eb08
📒 Files selected for processing (35)
src/microsvc/mod.rssrc/microsvc/service.rstests/distributed_read_model/checkout_saga_service/handlers/record_seat_reserved.rstests/distributed_read_model/checkout_saga_service/handlers/start.rstests/distributed_read_model/checkout_saga_service/service.rstests/distributed_read_model/main.rstests/distributed_read_model/projection_service/handlers/checkout.rstests/distributed_read_model/projection_service/handlers/mod.rstests/distributed_read_model/projection_service/handlers/seat.rstests/distributed_read_model/projection_service/mod.rstests/distributed_read_model/projection_service/service.rstests/distributed_read_model/seat_inventory_service/handlers/add.rstests/distributed_read_model/seat_inventory_service/handlers/reserve_started_checkout_seat.rstests/distributed_read_model/seat_inventory_service/service.rstests/distributed_read_model_board/board_service/handlers/board_add_card.rstests/distributed_read_model_board/board_service/handlers/board_move_card.rstests/distributed_read_model_board/board_service/handlers/board_open.rstests/distributed_read_model_board/board_service/handlers/board_remove_card.rstests/distributed_read_model_board/projections_service/handlers/board.rstests/distributed_read_model_board/projections_service/handlers/mod.rstests/distributed_read_model_board/projections_service/mod.rstests/microsvc/convention.rstests/microsvc/handlers/counter_create.rstests/microsvc/handlers/counter_increment.rstests/microsvc/handlers/whoami.rstests/sagas/handlers/inventory/init.rstests/sagas/handlers/inventory/reserve.rstests/sagas/handlers/orders/complete.rstests/sagas/handlers/orders/create.rstests/sagas/handlers/payments/process.rstests/sagas/handlers/saga/on_inventory_reserved.rstests/sagas/handlers/saga/on_order_completed.rstests/sagas/handlers/saga/on_order_created.rstests/sagas/handlers/saga/on_payment_succeeded.rstests/sagas/handlers/saga/start.rs
Removes per-handler SPEC constants and has register_handlers! construct HandlerSpec values from COMMAND, EVENT, and EVENTS constants.
Drops the compatibility Service::command, Service::command_guarded, and Service::commands APIs and updates tests/docs to use HandlerSpec registration.
Adds HandlerBuilder so command/event registration can use .handle(...) or .guarded(...), with envelope selection before registration.
Removes envelope input modes so handlers always get ctx.message() plus ctx.input::<T>() for JSON payload decoding.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/microsvc/service.rs (1)
791-793:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winNormalize metadata keys for consistent session lookups.
message_to_session()preserves metadata key casing, but the HTTP transport lowercases headers before creatingSession. This makesctx.user_id()andctx.role()transport-dependent when metadata arrives with different casing (e.g.,X-Hasura-User-Idvsx-hasura-user-id).Suggested fix
fn message_to_session(message: &Message) -> Session { - let vars: HashMap<String, String> = message.metadata.iter().cloned().collect(); + let vars: HashMap<String, String> = message + .metadata + .iter() + .map(|(key, value)| (key.to_ascii_lowercase(), value.clone())) + .collect(); Session::from_map(vars) }🤖 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/microsvc/service.rs` around lines 791 - 793, message_to_session currently preserves the original metadata key casing which leads to transport-dependent lookups (e.g., X-Hasura-User-Id vs x-hasura-user-id); update message_to_session (where vars: HashMap<String,String> is built and passed to Session::from_map) to normalize keys the same way HTTP transport does — convert all metadata keys to lowercase (or apply the same canonical normalization) before inserting into vars so ctx.user_id()/ctx.role() work consistently regardless of header casing.
🤖 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 `@src/microsvc/service.rs`:
- Around line 243-250: The payload_bitcode function currently uses
bitcode::deserialize while bounding T: serde::de::DeserializeOwned, causing a
trait mismatch; update payload_bitcode to call
bitcode::serde::deserialize(&self.payload) (keeping T:
serde::de::DeserializeOwned) OR alternatively change the generic bound to T:
bitcode::Decode and keep bitcode::deserialize. Locate the function
payload_bitcode in service.rs and replace the deserialize call accordingly
and/or adjust the trait bound to match the chosen bitcode::deserialize variant
so the types align.
---
Duplicate comments:
In `@src/microsvc/service.rs`:
- Around line 791-793: message_to_session currently preserves the original
metadata key casing which leads to transport-dependent lookups (e.g.,
X-Hasura-User-Id vs x-hasura-user-id); update message_to_session (where vars:
HashMap<String,String> is built and passed to Session::from_map) to normalize
keys the same way HTTP transport does — convert all metadata keys to lowercase
(or apply the same canonical normalization) before inserting into vars so
ctx.user_id()/ctx.role() work consistently regardless of header casing.
🪄 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: addd8cbd-a960-467f-b3b8-4ddf98b31882
📒 Files selected for processing (10)
src/microsvc/context.rssrc/microsvc/mod.rssrc/microsvc/service.rstests/distributed_read_model/projection_service/handlers/checkout.rstests/distributed_read_model/projection_service/handlers/mod.rstests/distributed_read_model/projection_service/handlers/seat.rstests/distributed_read_model/projection_service/service.rstests/distributed_read_model_board/projections_service/handlers/board.rstests/distributed_read_model_board/projections_service/handlers/mod.rstests/distributed_read_model_board/projections_service/mod.rs
🚧 Files skipped from review as they are similar to previous changes (3)
- tests/distributed_read_model/projection_service/handlers/seat.rs
- tests/distributed_read_model/projection_service/service.rs
- tests/distributed_read_model/projection_service/handlers/mod.rs
| pub fn payload_bitcode<T: serde::de::DeserializeOwned>(&self) -> Result<T, HandlerError> { | ||
| bitcode::deserialize(&self.payload).map_err(|e| { | ||
| HandlerError::DecodeFailed(format!( | ||
| "invalid bitcode payload for message '{}': {}", | ||
| self.name, e | ||
| )) | ||
| }) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
bitcode 0.6 rust crate deserialize trait requirements
💡 Result:
In the bitcode crate version 0.6, the requirements for deserialization depend on whether you are using the crate's native bitcode traits or its optional Serde integration [1][2][3]. Native Traits: If you are using bitcode's native API, the function bitcode::deserialize requires the type T to implement the bitcode::Decode trait [4]. To implement Decode for your custom types, the recommended approach is to use the provided derive macro, #[derive(Decode)] [5], which is available when the "derive" feature is enabled (this is part of the default feature set) [2][3]. Serde Integration: The bitcode crate also supports Serde as an optional feature [1][3]. If you choose to use serde::Deserialize (often via bitcode::serde::deserialize), the requirement is that your type T must implement the serde::Deserialize trait [6]. This integration is gated behind the "serde" feature flag in your Cargo.toml [1][3]. Key Implementation Requirements: 1. Feature Flags: Ensure the "derive" feature is enabled in your Cargo.toml to use #[derive(Decode)] [2][3]. If using Serde, you must explicitly enable the "serde" feature [1][3]. 2. Trait Bounds: - For native decoding: The type must satisfy T: Decode [6][4]. - For Serde decoding: The type must satisfy serde::Deserialize [6]. 3. Constraints: The bitcode::Decode trait is not dyn-compatible (object-safe) [5]. For most use cases, adding #[derive(bitcode::Decode, bitcode::Encode)] to your structs or enums is the standard way to satisfy the requirements for bitcode's native binary format [2][3][5].
Citations:
- 1: https://docs.rs/crate/bitcode/^0.6
- 2: https://crates.io/crates/bitcode/0.6.6
- 3: https://crates.io/crates/bitcode
- 4: https://docs.rs/bitcode/latest/bitcode/fn.deserialize.html
- 5: https://docs.rs/bitcode/latest/bitcode/trait.Decode.html
- 6: https://docs.rs/bitcode
Fix payload_bitcode trait mismatch with bitcode::deserialize
In src/microsvc/service.rs (lines 243-250), payload_bitcode<T: serde::de::DeserializeOwned> calls bitcode::deserialize(&self.payload), but in bitcode 0.6 the native bitcode::deserialize expects T: bitcode::Decode (Serde integration uses bitcode::serde::deserialize for T: serde::Deserialize). Update the call to bitcode::serde::deserialize or change the bound to bitcode::Decode.
🤖 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/microsvc/service.rs` around lines 243 - 250, The payload_bitcode function
currently uses bitcode::deserialize while bounding T:
serde::de::DeserializeOwned, causing a trait mismatch; update payload_bitcode to
call bitcode::serde::deserialize(&self.payload) (keeping T:
serde::de::DeserializeOwned) OR alternatively change the generic bound to T:
bitcode::Decode and keep bitcode::deserialize. Locate the function
payload_bitcode in service.rs and replace the deserialize call accordingly
and/or adjust the trait bound to match the chosen bitcode::deserialize variant
so the types align.
There was a problem hiding this comment.
I left this unchanged because this finding does not apply to the crate version/features in this repo. Cargo.toml uses bitcode = 0.6.9 with features = ["serde"], and that crate re-exports the serde serializer/deserializer at the crate root via pub use crate::serde::*. cargo test --all-features compiles and passes with payload_bitcode<T: DeserializeOwned>, so there is no trait-bound mismatch here.
There was a problem hiding this comment.
@patrickleet, thanks for the clarification — that makes sense. With bitcode = "0.6.9" and features = ["serde"] enabled, the crate re-exports its serde integration at the crate root (pub use crate::serde::*), so bitcode::deserialize already accepts T: serde::de::DeserializeOwned and there is no trait-bound mismatch. I'll record this for future reviews.
✏️ Learnings added
Learnt from: patrickleet
Repo: patrickleet/sourced_rust PR: 43
File: src/microsvc/service.rs:249-256
Timestamp: 2026-05-27T20:53:23.241Z
Learning: In `src/microsvc/service.rs` (Rust), `payload_bitcode<T: serde::de::DeserializeOwned>` calls `bitcode::deserialize(&self.payload)`. This is valid and compiles correctly because `Cargo.toml` uses `bitcode = "0.6.9"` with `features = ["serde"]`, which re-exports the serde-based deserializer at the crate root via `pub use crate::serde::*`. Do not flag this as a trait-bound mismatch; `cargo test --all-features` passes without changes.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
Summary
Tests
Notes
Summary by CodeRabbit
New Features
Refactor
Documentation
Tests
Bug Fixes