#![forbid(unsafe_code)]
#![warn(missing_docs)]
use std::sync::Mutex;
use pacta_contract::{Claim, Pact, Registry, Retainer, Timestamp};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NotHeld;
impl std::fmt::Display for NotHeld {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "retainer is not the current holder of any claim")
}
}
impl std::error::Error for NotHeld {}
enum State {
Available,
Held { retainer: Uuid, expiry: Timestamp },
Settled,
}
struct Record {
pact: Pact,
state: State,
}
pub struct MemoryRegistry {
records: Mutex<Vec<Record>>,
lease_millis: u64,
}
impl MemoryRegistry {
#[must_use]
pub fn new(lease_millis: u64) -> Self {
Self::seeded(Vec::new(), lease_millis)
}
#[must_use]
pub fn seeded(pacts: Vec<Pact>, lease_millis: u64) -> Self {
Self {
records: Mutex::new(
pacts
.into_iter()
.map(|pact| Record {
pact,
state: State::Available,
})
.collect(),
),
lease_millis,
}
}
fn find_holder(records: &mut [Record], retainer: &Retainer) -> Option<usize> {
records.iter().position(|record| {
matches!(record.state, State::Held { retainer: held, .. } if held == retainer.id())
})
}
}
impl Registry for MemoryRegistry {
type Error = NotHeld;
fn claim(&self, dockets: &[&str], now: Timestamp) -> Result<Option<Claim>, Self::Error> {
let mut records = self
.records
.lock()
.expect("registry mutex should not be poisoned");
let claimable = records.iter().position(|record| {
if !dockets.contains(&record.pact.docket.as_str()) {
return false;
}
match record.state {
State::Available => true,
State::Held { expiry, .. } => expiry < now,
State::Settled => false,
}
});
let Some(index) = claimable else {
return Ok(None);
};
let retainer = Retainer::new(Uuid::new_v4());
let expiry = now.plus_millis(self.lease_millis);
records[index].state = State::Held {
retainer: retainer.id(),
expiry,
};
Ok(Some(Claim::new(
records[index].pact.clone(),
retainer,
expiry,
)))
}
fn heartbeat(&self, retainer: &Retainer, now: Timestamp) -> Result<(), Self::Error> {
let mut records = self
.records
.lock()
.expect("registry mutex should not be poisoned");
let index = Self::find_holder(&mut records, retainer).ok_or(NotHeld)?;
let State::Held { expiry, .. } = records[index].state else {
return Err(NotHeld);
};
if expiry < now {
return Err(NotHeld);
}
records[index].state = State::Held {
retainer: retainer.id(),
expiry: now.plus_millis(self.lease_millis),
};
Ok(())
}
fn fulfill(&self, retainer: &Retainer) -> Result<(), Self::Error> {
self.settle(retainer)
}
fn breach(&self, retainer: &Retainer) -> Result<(), Self::Error> {
self.settle(retainer)
}
}
impl MemoryRegistry {
fn settle(&self, retainer: &Retainer) -> Result<(), NotHeld> {
let mut records = self
.records
.lock()
.expect("registry mutex should not be poisoned");
let index = Self::find_holder(&mut records, retainer).ok_or(NotHeld)?;
records[index].state = State::Settled;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn passes_registry_conformance() {
pacta_conformance::run(MemoryRegistry::seeded);
}
}