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§
Sourcefn 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,
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§
Sourcefn 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 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.
Sourcefn 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,
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".