Skip to main content

claw_core/
id.rs

1use data_encoding::BASE32_NOPAD;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use ulid::Ulid;
5
6use crate::CoreError;
7
8const OBJECT_ID_PREFIX: &str = "clw_";
9
10/// Content-addressed identifier for a stored Claw object.
11#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct ObjectId([u8; 32]);
13
14impl ObjectId {
15    /// Construct an object ID from raw 32-byte hash output.
16    pub fn from_bytes(bytes: [u8; 32]) -> Self {
17        Self(bytes)
18    }
19
20    /// Borrow the raw 32-byte hash output.
21    pub fn as_bytes(&self) -> &[u8; 32] {
22        &self.0
23    }
24
25    /// Format the ID as lowercase hexadecimal.
26    pub fn to_hex(&self) -> String {
27        hex::encode(self.0)
28    }
29
30    /// Parse a lowercase or uppercase hexadecimal object ID.
31    pub fn from_hex(s: &str) -> Result<Self, CoreError> {
32        let bytes = hex::decode(s).map_err(|e| CoreError::InvalidObjectId(e.to_string()))?;
33        let arr: [u8; 32] = bytes
34            .try_into()
35            .map_err(|_| CoreError::InvalidObjectId("expected 32 bytes".into()))?;
36        Ok(Self(arr))
37    }
38
39    /// Parse the human-facing `clw_` base32 display form.
40    pub fn from_display(s: &str) -> Result<Self, CoreError> {
41        let encoded = s.strip_prefix(OBJECT_ID_PREFIX).ok_or_else(|| {
42            CoreError::InvalidObjectId(format!("missing prefix '{OBJECT_ID_PREFIX}'"))
43        })?;
44        let upper = encoded.to_uppercase();
45        let bytes = BASE32_NOPAD
46            .decode(upper.as_bytes())
47            .map_err(|e| CoreError::InvalidObjectId(e.to_string()))?;
48        let arr: [u8; 32] = bytes
49            .try_into()
50            .map_err(|_| CoreError::InvalidObjectId("expected 32 bytes".into()))?;
51        Ok(Self(arr))
52    }
53
54    /// First 2 hex chars used for loose object directory sharding
55    pub fn shard_prefix(&self) -> String {
56        hex::encode(&self.0[..1])
57    }
58
59    /// Remaining hex chars for the loose object filename
60    pub fn shard_suffix(&self) -> String {
61        hex::encode(&self.0[1..])
62    }
63}
64
65impl fmt::Display for ObjectId {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        let encoded = BASE32_NOPAD.encode(&self.0).to_lowercase();
68        write!(f, "{OBJECT_ID_PREFIX}{encoded}")
69    }
70}
71
72impl fmt::Debug for ObjectId {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        write!(f, "ObjectId({})", self)
75    }
76}
77
78/// Stable ULID identifier for an intent object.
79#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
80pub struct IntentId(Ulid);
81
82impl IntentId {
83    /// Generate a new time-sortable intent ID.
84    pub fn new() -> Self {
85        Self(Ulid::new())
86    }
87
88    /// Construct an intent ID from raw ULID bytes.
89    pub fn from_bytes(bytes: [u8; 16]) -> Self {
90        Self(Ulid::from_bytes(bytes))
91    }
92
93    /// Return the raw 16-byte ULID representation.
94    pub fn as_bytes(&self) -> [u8; 16] {
95        self.0.to_bytes()
96    }
97
98    /// Parse an intent ID from canonical ULID text.
99    pub fn from_string(s: &str) -> Result<Self, CoreError> {
100        let ulid = Ulid::from_string(s).map_err(|e| CoreError::InvalidObjectId(e.to_string()))?;
101        Ok(Self(ulid))
102    }
103}
104
105impl Default for IntentId {
106    fn default() -> Self {
107        Self::new()
108    }
109}
110
111impl fmt::Display for IntentId {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(f, "{}", self.0)
114    }
115}
116
117impl fmt::Debug for IntentId {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        write!(f, "IntentId({})", self.0)
120    }
121}
122
123/// Stable ULID identifier for a change object.
124#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
125pub struct ChangeId(Ulid);
126
127impl ChangeId {
128    /// Generate a new time-sortable change ID.
129    pub fn new() -> Self {
130        Self(Ulid::new())
131    }
132
133    /// Construct a change ID from raw ULID bytes.
134    pub fn from_bytes(bytes: [u8; 16]) -> Self {
135        Self(Ulid::from_bytes(bytes))
136    }
137
138    /// Return the raw 16-byte ULID representation.
139    pub fn as_bytes(&self) -> [u8; 16] {
140        self.0.to_bytes()
141    }
142
143    /// Parse a change ID from canonical ULID text.
144    pub fn from_string(s: &str) -> Result<Self, CoreError> {
145        let ulid = Ulid::from_string(s).map_err(|e| CoreError::InvalidObjectId(e.to_string()))?;
146        Ok(Self(ulid))
147    }
148}
149
150impl Default for ChangeId {
151    fn default() -> Self {
152        Self::new()
153    }
154}
155
156impl fmt::Display for ChangeId {
157    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158        write!(f, "{}", self.0)
159    }
160}
161
162impl fmt::Debug for ChangeId {
163    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164        write!(f, "ChangeId({})", self.0)
165    }
166}
167
168/// Stable ULID identifier for a conflict object.
169#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
170pub struct ConflictId(Ulid);
171
172impl ConflictId {
173    /// Generate a new time-sortable conflict ID.
174    pub fn new() -> Self {
175        Self(Ulid::new())
176    }
177
178    /// Construct a conflict ID from raw ULID bytes.
179    pub fn from_bytes(bytes: [u8; 16]) -> Self {
180        Self(Ulid::from_bytes(bytes))
181    }
182
183    /// Return the raw 16-byte ULID representation.
184    pub fn as_bytes(&self) -> [u8; 16] {
185        self.0.to_bytes()
186    }
187}
188
189impl Default for ConflictId {
190    fn default() -> Self {
191        Self::new()
192    }
193}
194
195impl fmt::Display for ConflictId {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        write!(f, "{}", self.0)
198    }
199}
200
201impl fmt::Debug for ConflictId {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        write!(f, "ConflictId({})", self.0)
204    }
205}