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#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12pub struct ObjectId([u8; 32]);
13
14impl ObjectId {
15 pub fn from_bytes(bytes: [u8; 32]) -> Self {
17 Self(bytes)
18 }
19
20 pub fn as_bytes(&self) -> &[u8; 32] {
22 &self.0
23 }
24
25 pub fn to_hex(&self) -> String {
27 hex::encode(self.0)
28 }
29
30 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 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 pub fn shard_prefix(&self) -> String {
56 hex::encode(&self.0[..1])
57 }
58
59 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#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
80pub struct IntentId(Ulid);
81
82impl IntentId {
83 pub fn new() -> Self {
85 Self(Ulid::new())
86 }
87
88 pub fn from_bytes(bytes: [u8; 16]) -> Self {
90 Self(Ulid::from_bytes(bytes))
91 }
92
93 pub fn as_bytes(&self) -> [u8; 16] {
95 self.0.to_bytes()
96 }
97
98 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#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
125pub struct ChangeId(Ulid);
126
127impl ChangeId {
128 pub fn new() -> Self {
130 Self(Ulid::new())
131 }
132
133 pub fn from_bytes(bytes: [u8; 16]) -> Self {
135 Self(Ulid::from_bytes(bytes))
136 }
137
138 pub fn as_bytes(&self) -> [u8; 16] {
140 self.0.to_bytes()
141 }
142
143 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#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
170pub struct ConflictId(Ulid);
171
172impl ConflictId {
173 pub fn new() -> Self {
175 Self(Ulid::new())
176 }
177
178 pub fn from_bytes(bytes: [u8; 16]) -> Self {
180 Self(Ulid::from_bytes(bytes))
181 }
182
183 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}