a3s_code_core/store/
lease.rs1use anyhow::{bail, Result};
8use serde::{Deserialize, Serialize};
9
10pub const SESSION_STORE_WRITER_LEASE_SCHEMA_V1: &str = "a3s.code.session-store-writer-lease.v1";
11
12#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
15#[serde(rename_all = "camelCase", deny_unknown_fields)]
16pub struct SessionStoreWriterLeaseV1 {
17 pub schema: String,
18 pub epoch: u64,
20 pub holder_id: String,
22 pub acquired_at_ms: u64,
23}
24
25impl SessionStoreWriterLeaseV1 {
26 pub fn new(epoch: u64, holder_id: impl Into<String>, acquired_at_ms: u64) -> Result<Self> {
27 let lease = Self {
28 schema: SESSION_STORE_WRITER_LEASE_SCHEMA_V1.to_owned(),
29 epoch,
30 holder_id: holder_id.into(),
31 acquired_at_ms,
32 };
33 lease.validate()?;
34 Ok(lease)
35 }
36
37 pub fn validate(&self) -> Result<()> {
38 if self.schema != SESSION_STORE_WRITER_LEASE_SCHEMA_V1 {
39 bail!("session store writer lease schema is unsupported");
40 }
41 if self.epoch == 0 {
42 bail!("session store writer lease epoch must be one-based");
43 }
44 if self.holder_id.trim().is_empty() {
45 bail!("session store writer lease holder_id must be non-empty");
46 }
47 Ok(())
48 }
49
50 pub fn matches_holder(&self, other: &Self) -> bool {
51 self.epoch == other.epoch && self.holder_id == other.holder_id
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn writer_lease_rejects_empty_holder_and_zero_epoch() {
61 assert!(SessionStoreWriterLeaseV1::new(0, "a", 1).is_err());
62 assert!(SessionStoreWriterLeaseV1::new(1, " ", 1).is_err());
63 let ok = SessionStoreWriterLeaseV1::new(1, "worker-a", 10).unwrap();
64 assert_eq!(ok.epoch, 1);
65 assert!(ok.matches_holder(&ok));
66 }
67}