macro_rules! idempotency_guard {
($events:expr,
$(already_applied: $pattern:pat $(if $guard:expr)? ,)+
resets_on: $break_pattern:pat $(if $break_guard:expr)? $(,)?) => { ... };
($events:expr,
$(already_applied: $pattern:pat $(if $guard:expr)?),+ $(,)?) => { ... };
}Expand description
Prevent duplicate event processing by checking for idempotent operations.
Guards against replaying the same mutation in event-sourced systems.
Returns AlreadyApplied early if matching events are found, allowing the caller
to skip redundant operations. Use resets_on to allow re-applying after an intervening event.
§Parameters
$events: Event collection to search (usually chronologically reversed)already_applied:One or more event patterns that indicate the operation was already applied. Multiple patterns are supported — each is checked independently.resets_on:Optional event pattern that resets the guard, allowing re-execution. Use Rust’s native or-pattern (P1 | P2) to match multiple reset events.
When iterating events in reverse, if a resets_on event is found before the
already_applied event, the guard allows re-execution. This is useful for
toggle-like operations (freeze/unfreeze) or when a state change should
invalidate a previous idempotency check.
§Examples
use es_entity::{idempotency_guard, Idempotent};
pub enum UserEvent{
Initialized {id: u64, name: String},
NameUpdated {name: String}
}
pub struct User{
events: Vec<UserEvent>
}
impl User{
pub fn update_name(&mut self, new_name: impl Into<String>) -> Idempotent<()>{
let name = new_name.into();
idempotency_guard!(
self.events.iter().rev(),
already_applied: UserEvent::NameUpdated { name: existing_name } if existing_name == &name
);
self.events.push(UserEvent::NameUpdated{name});
Idempotent::Executed(())
}
pub fn update_name_resettable(&mut self, new_name: impl Into<String>) -> Idempotent<()>{
let name = new_name.into();
idempotency_guard!(
self.events.iter().rev(),
already_applied: UserEvent::NameUpdated { name: existing_name } if existing_name == &name,
resets_on: UserEvent::NameUpdated {..}
// if any other NameUpdated happened more recently, allow re-applying
);
self.events.push(UserEvent::NameUpdated{name});
Idempotent::Executed(())
}
}
let mut user1 = User{ events: vec![] };
let mut user2 = User{ events: vec![] };
assert!(user1.update_name("Alice").did_execute());
// updating "Alice" again ignored because same event with same name exists
assert!(user1.update_name("Alice").was_already_applied());
assert!(user2.update_name_resettable("Alice").did_execute());
assert!(user2.update_name_resettable("Bob").did_execute());
// updating "Alice" again works because Bob's NameUpdated resets the guard
assert!(user2.update_name_resettable("Alice").did_execute());§Multiple already_applied patterns
use es_entity::{idempotency_guard, Idempotent};
pub enum ConfigEvent {
Initialized { id: u64 },
Updated { key: String, value: String },
KeyRotated { key: String },
}
pub struct Config {
events: Vec<ConfigEvent>,
}
impl Config {
pub fn apply_change(&mut self, key: String, value: String) -> Idempotent<()> {
idempotency_guard!(
self.events.iter().rev(),
already_applied: ConfigEvent::Updated { key: k, value: v } if k == &key && v == &value,
already_applied: ConfigEvent::KeyRotated { key: k } if k == &key,
resets_on: ConfigEvent::Initialized { .. }
);
self.events.push(ConfigEvent::Updated { key, value });
Idempotent::Executed(())
}
}
let mut config = Config { events: vec![] };
assert!(config.apply_change("k".into(), "v".into()).did_execute());
assert!(config.apply_change("k".into(), "v".into()).was_already_applied());