1#![forbid(unsafe_code)]
14#![warn(missing_docs)]
15
16use std::sync::Mutex;
17
18use pacta_contract::{Claim, Pact, Registry, Retainer, Timestamp};
19use uuid::Uuid;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct NotHeld;
25
26impl std::fmt::Display for NotHeld {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 write!(f, "retainer is not the current holder of any claim")
29 }
30}
31
32impl std::error::Error for NotHeld {}
33
34enum State {
35 Available,
36 Held { retainer: Uuid, expiry: Timestamp },
37 Settled,
38}
39
40struct Record {
41 pact: Pact,
42 state: State,
43}
44
45pub struct MemoryRegistry {
47 records: Mutex<Vec<Record>>,
48 lease_millis: u64,
49}
50
51impl MemoryRegistry {
52 #[must_use]
54 pub fn new(lease_millis: u64) -> Self {
55 Self::seeded(Vec::new(), lease_millis)
56 }
57
58 #[must_use]
61 pub fn seeded(pacts: Vec<Pact>, lease_millis: u64) -> Self {
62 Self {
63 records: Mutex::new(
64 pacts
65 .into_iter()
66 .map(|pact| Record {
67 pact,
68 state: State::Available,
69 })
70 .collect(),
71 ),
72 lease_millis,
73 }
74 }
75
76 fn find_holder(records: &mut [Record], retainer: &Retainer) -> Option<usize> {
77 records.iter().position(|record| {
78 matches!(record.state, State::Held { retainer: held, .. } if held == retainer.id())
79 })
80 }
81}
82
83impl Registry for MemoryRegistry {
84 type Error = NotHeld;
85
86 fn claim(&self, dockets: &[&str], now: Timestamp) -> Result<Option<Claim>, Self::Error> {
87 let mut records = self
88 .records
89 .lock()
90 .expect("registry mutex should not be poisoned");
91 let claimable = records.iter().position(|record| {
92 if !dockets.contains(&record.pact.docket.as_str()) {
93 return false;
94 }
95 match record.state {
96 State::Available => true,
97 State::Held { expiry, .. } => expiry < now,
99 State::Settled => false,
100 }
101 });
102
103 let Some(index) = claimable else {
105 return Ok(None);
106 };
107 let retainer = Retainer::new(Uuid::new_v4());
108 let expiry = now.plus_millis(self.lease_millis);
109 records[index].state = State::Held {
110 retainer: retainer.id(),
111 expiry,
112 };
113 Ok(Some(Claim::new(
114 records[index].pact.clone(),
115 retainer,
116 expiry,
117 )))
118 }
119
120 fn heartbeat(&self, retainer: &Retainer, now: Timestamp) -> Result<(), Self::Error> {
121 let mut records = self
122 .records
123 .lock()
124 .expect("registry mutex should not be poisoned");
125 let index = Self::find_holder(&mut records, retainer).ok_or(NotHeld)?;
126 let State::Held { expiry, .. } = records[index].state else {
127 return Err(NotHeld);
128 };
129 if expiry < now {
131 return Err(NotHeld);
132 }
133 records[index].state = State::Held {
134 retainer: retainer.id(),
135 expiry: now.plus_millis(self.lease_millis),
136 };
137 Ok(())
138 }
139
140 fn fulfill(&self, retainer: &Retainer) -> Result<(), Self::Error> {
141 self.settle(retainer)
142 }
143
144 fn breach(&self, retainer: &Retainer) -> Result<(), Self::Error> {
145 self.settle(retainer)
146 }
147}
148
149impl MemoryRegistry {
150 fn settle(&self, retainer: &Retainer) -> Result<(), NotHeld> {
153 let mut records = self
154 .records
155 .lock()
156 .expect("registry mutex should not be poisoned");
157 let index = Self::find_holder(&mut records, retainer).ok_or(NotHeld)?;
158 records[index].state = State::Settled;
159 Ok(())
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn passes_registry_conformance() {
169 pacta_conformance::run(MemoryRegistry::seeded);
170 }
171}