Skip to main content

ReplayGuard

Trait ReplayGuard 

Source
pub trait ReplayGuard: Send + Sync {
    // Required method
    fn claim<'life0, 'life1, 'life2, 'async_trait>(
        &'life0 self,
        id: &'life1 str,
        digest: &'life2 DocumentDigest,
        retain_until: Option<DateTime<Utc>>,
        now: DateTime<Utc>,
    ) -> Pin<Box<dyn Future<Output = Result<ReplayVerdict, ReplayGuardError>> + Send + 'async_trait>>
       where 'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait,
             Self: 'async_trait;

    // Provided methods
    fn record_response<'life0, 'life1, 'life2, 'async_trait>(
        &'life0 self,
        id: &'life1 str,
        response: Option<&'life2 Value>,
    ) -> Pin<Box<dyn Future<Output = Result<(), ReplayGuardError>> + Send + 'async_trait>>
       where 'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait,
             Self: 'async_trait { ... }
    fn release<'life0, 'life1, 'life2, 'async_trait>(
        &'life0 self,
        id: &'life1 str,
        digest: &'life2 DocumentDigest,
    ) -> Pin<Box<dyn Future<Output = Result<(), ReplayGuardError>> + Send + 'async_trait>>
       where 'life0: 'async_trait,
             'life1: 'async_trait,
             'life2: 'async_trait,
             Self: 'async_trait { ... }
}
Expand description

The consumer-side record that makes SPEC §7.2 item 11 true.

Object-safe and async (via async-trait), so a deployment can hand consume_inbound a &dyn ReplayGuard backed by Redis, Postgres, DynamoDB, or anything else that survives a process restart. InMemoryReplayGuard is the batteries-included default and is correct for a single-process consumer; it is not correct behind a load balancer, where two replicas would each accept the same document once. See its documentation.

Required Methods§

Source

fn claim<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, id: &'life1 str, digest: &'life2 DocumentDigest, retain_until: Option<DateTime<Utc>>, now: DateTime<Utc>, ) -> Pin<Box<dyn Future<Output = Result<ReplayVerdict, ReplayGuardError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Claim id for execution on behalf of a document with content identity digest.

retain_until is the instant past which the record may be dropped — FreshnessPolicy::record_expiry, which SPEC §7.2 makes the same instant as the end of the consumer’s willingness to execute the document. An implementation SHOULD treat a record whose retain_until has passed as absent, so that the key is released rather than conflicting forever with a document nobody would execute.

Implementations MUST make claim-and-record atomic with respect to concurrent calls: two simultaneous deliveries of the same document must not both receive ReplayVerdict::Fresh. That is the whole guarantee.

Provided Methods§

Source

fn record_response<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, id: &'life1 str, response: Option<&'life2 Value>, ) -> Pin<Box<dyn Future<Output = Result<(), ReplayGuardError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Attach the response a completed execution produced, so a later duplicate can be answered with it per SPEC §7.2 (Disposition of a duplicate) rather than merely absorbed in silence.

Optional: the default implementation records nothing, which yields Duplicate { prior_response: None, in_flight: false } on a re-arrival. That still satisfies item 11 — the effect does not happen twice — and is the right shape for a fire-and-forget specification, which has no response to return.

Source

fn release<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, id: &'life1 str, digest: &'life2 DocumentDigest, ) -> Pin<Box<dyn Future<Output = Result<(), ReplayGuardError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, Self: 'async_trait,

Release a claim whose execution never began — for example because a check after the claim refused the document.

Without this, a document rejected downstream of the claim would burn its id and a corrected resend under the same id would come back idConflict forever. The default implementation does nothing, which is safe but leaves that record in place until retain_until.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementations on Foreign Types§

Source§

impl<T> ReplayGuard for &T
where T: ReplayGuard + ?Sized,

Source§

fn claim<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, id: &'life1 str, digest: &'life2 DocumentDigest, retain_until: Option<DateTime<Utc>>, now: DateTime<Utc>, ) -> Pin<Box<dyn Future<Output = Result<ReplayVerdict, ReplayGuardError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, &T: 'async_trait,

Source§

fn record_response<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, id: &'life1 str, response: Option<&'life2 Value>, ) -> Pin<Box<dyn Future<Output = Result<(), ReplayGuardError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, &T: 'async_trait,

Source§

fn release<'life0, 'life1, 'life2, 'async_trait>( &'life0 self, id: &'life1 str, digest: &'life2 DocumentDigest, ) -> Pin<Box<dyn Future<Output = Result<(), ReplayGuardError>> + Send + 'async_trait>>
where 'life0: 'async_trait, 'life1: 'async_trait, 'life2: 'async_trait, &T: 'async_trait,

Implementors§