Skip to main content

idempotency_guard

Macro idempotency_guard 

Source
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());