1use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
11pub enum HashAlgorithm {
12 Sha256,
14 Blake3,
16}
17
18impl HashAlgorithm {
19 pub fn prefix(&self) -> &'static str {
21 match self {
22 HashAlgorithm::Sha256 => "sha256",
23 HashAlgorithm::Blake3 => "blake3",
24 }
25 }
26
27 pub fn hash_len(&self) -> usize {
29 match self {
30 HashAlgorithm::Sha256 => 32,
31 HashAlgorithm::Blake3 => 32,
32 }
33 }
34
35 pub fn hex_len(&self) -> usize {
37 self.hash_len() * 2
38 }
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43pub enum IdError {
44 InvalidFormat(String),
46 InvalidLength { expected: usize, got: usize },
48 UnknownAlgorithm(String),
50 InvalidHex(String),
52}
53
54impl std::fmt::Display for IdError {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 match self {
57 IdError::InvalidFormat(s) => write!(f, "Invalid ID format: {s}"),
58 IdError::InvalidLength { expected, got } => {
59 write!(f, "Invalid ID length: expected {expected}, got {got}")
60 }
61 IdError::UnknownAlgorithm(alg) => write!(f, "Unknown hash algorithm: {alg}"),
62 IdError::InvalidHex(s) => write!(f, "Invalid hex characters: {s}"),
63 }
64 }
65}
66
67impl std::error::Error for IdError {}
68
69#[derive(Debug, Clone, PartialEq, Eq, Hash)]
78pub struct ID {
79 repr: String,
81 algorithm: HashAlgorithm,
83}
84
85impl PartialOrd for ID {
87 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
88 Some(self.cmp(other))
89 }
90}
91
92impl Ord for ID {
96 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
97 self.repr.cmp(&other.repr)
98 }
99}
100
101impl Default for ID {
102 fn default() -> Self {
103 Self {
104 repr: String::new(),
105 algorithm: HashAlgorithm::Sha256,
106 }
107 }
108}
109
110impl ID {
111 pub fn new(s: impl Into<String>) -> Self {
115 let repr = s.into();
116 let algorithm = Self::detect_algorithm(&repr);
117 Self { repr, algorithm }
118 }
119
120 pub fn from_bytes(data: impl AsRef<[u8]>) -> Self {
122 Self::from_bytes_with(data, HashAlgorithm::Sha256)
123 }
124
125 pub fn from_bytes_with(data: impl AsRef<[u8]>, algorithm: HashAlgorithm) -> Self {
127 let data = data.as_ref();
128 let hash_bytes = match algorithm {
129 HashAlgorithm::Sha256 => {
130 let mut hasher = Sha256::new();
131 hasher.update(data);
132 hasher.finalize().to_vec()
133 }
134 HashAlgorithm::Blake3 => blake3::hash(data).as_bytes().to_vec(),
135 };
136
137 let hex = hex::encode(&hash_bytes);
138 let repr = format!("{}:{}", algorithm.prefix(), hex);
139
140 Self { repr, algorithm }
141 }
142
143 pub fn parse(s: &str) -> Result<Self, IdError> {
147 if s.is_empty() {
148 return Ok(Self::default());
149 }
150
151 let Some(colon_pos) = s.find(':') else {
153 return Err(IdError::InvalidFormat(
154 "ID must have algorithm prefix (e.g., 'sha256:' or 'blake3:')".to_string(),
155 ));
156 };
157
158 let (prefix, hex_part) = s.split_at(colon_pos);
159 let hex_part = &hex_part[1..]; let algorithm = match prefix {
162 "sha256" => HashAlgorithm::Sha256,
163 "blake3" => HashAlgorithm::Blake3,
164 _ => return Err(IdError::UnknownAlgorithm(prefix.to_string())),
165 };
166
167 Self::validate_hex_format(hex_part, algorithm)?;
168
169 Ok(Self {
170 repr: s.to_string(),
171 algorithm,
172 })
173 }
174
175 fn validate_hex_format(hex: &str, algorithm: HashAlgorithm) -> Result<(), IdError> {
177 let expected_len = algorithm.hex_len();
178
179 if hex.len() != expected_len {
180 return Err(IdError::InvalidLength {
181 expected: expected_len,
182 got: hex.len(),
183 });
184 }
185
186 if !hex
188 .chars()
189 .all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
190 {
191 return Err(IdError::InvalidHex(hex.to_string()));
192 }
193
194 Ok(())
195 }
196
197 fn detect_algorithm(s: &str) -> HashAlgorithm {
199 if let Some(colon_pos) = s.find(':') {
200 let prefix = &s[..colon_pos];
201 match prefix {
202 "blake3" => HashAlgorithm::Blake3,
203 _ => HashAlgorithm::Sha256, }
205 } else {
206 HashAlgorithm::Sha256 }
208 }
209
210 pub fn as_str(&self) -> &str {
212 &self.repr
213 }
214
215 pub fn is_empty(&self) -> bool {
217 self.repr.is_empty()
218 }
219
220 pub fn algorithm(&self) -> HashAlgorithm {
222 self.algorithm
223 }
224
225 pub fn hex(&self) -> &str {
227 if let Some(colon_pos) = self.repr.find(':') {
228 &self.repr[colon_pos + 1..]
229 } else {
230 &self.repr
231 }
232 }
233
234 pub fn as_bytes(&self) -> Result<Vec<u8>, hex::FromHexError> {
236 hex::decode(self.hex())
237 }
238}
239
240impl From<String> for ID {
242 fn from(s: String) -> Self {
243 Self::new(s)
244 }
245}
246
247impl From<&str> for ID {
248 fn from(s: &str) -> Self {
249 Self::new(s)
250 }
251}
252
253impl From<&ID> for ID {
254 fn from(id: &ID) -> Self {
255 id.clone()
256 }
257}
258
259impl AsRef<str> for ID {
260 fn as_ref(&self) -> &str {
261 &self.repr
262 }
263}
264
265impl std::fmt::Display for ID {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 write!(f, "{}", &self.repr)
268 }
269}
270
271impl std::ops::Deref for ID {
272 type Target = str;
273
274 fn deref(&self) -> &Self::Target {
275 &self.repr
276 }
277}
278
279impl PartialEq<str> for ID {
280 fn eq(&self, other: &str) -> bool {
281 self.repr == other
282 }
283}
284
285impl PartialEq<&str> for ID {
286 fn eq(&self, other: &&str) -> bool {
287 self.repr == *other
288 }
289}
290
291impl PartialEq<String> for ID {
292 fn eq(&self, other: &String) -> bool {
293 &self.repr == other
294 }
295}
296
297impl PartialEq<ID> for str {
298 fn eq(&self, other: &ID) -> bool {
299 self == other.repr
300 }
301}
302
303impl PartialEq<ID> for &str {
304 fn eq(&self, other: &ID) -> bool {
305 *self == other.repr
306 }
307}
308
309impl PartialEq<ID> for String {
310 fn eq(&self, other: &ID) -> bool {
311 self == &other.repr
312 }
313}
314
315impl From<ID> for String {
316 fn from(id: ID) -> Self {
317 id.repr
318 }
319}
320
321impl PartialEq<&ID> for ID {
322 fn eq(&self, other: &&ID) -> bool {
323 self == *other
324 }
325}
326
327impl From<&ID> for String {
328 fn from(id: &ID) -> Self {
329 id.repr.clone()
330 }
331}
332
333impl Serialize for ID {
338 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
339 where
340 S: serde::Serializer,
341 {
342 self.repr.serialize(serializer)
343 }
344}
345
346impl<'de> Deserialize<'de> for ID {
347 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
348 where
349 D: serde::Deserializer<'de>,
350 {
351 let s = String::deserialize(deserializer)?;
352 Ok(Self::new(s))
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359
360 #[test]
361 fn test_sha256_prefixed_format() {
362 let data = b"hello world";
363 let id = ID::from_bytes(data);
364
365 assert!(id.as_str().starts_with("sha256:"));
367 assert_eq!(id.algorithm(), HashAlgorithm::Sha256);
368 assert_eq!(id.as_str().len(), 71); }
370
371 #[test]
372 fn test_blake3_prefixed_format() {
373 let data = b"hello world";
374 let id = ID::from_bytes_with(data, HashAlgorithm::Blake3);
375
376 assert!(id.as_str().starts_with("blake3:"));
378 assert_eq!(id.algorithm(), HashAlgorithm::Blake3);
379 }
380
381 #[test]
382 fn test_parse_sha256_prefixed() {
383 let hex = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
384 let prefixed = format!("sha256:{hex}");
385 let id = ID::parse(&prefixed).unwrap();
386
387 assert_eq!(id.algorithm(), HashAlgorithm::Sha256);
388 assert_eq!(id.hex(), hex);
389 assert_eq!(id.as_str(), prefixed);
390 }
391
392 #[test]
393 fn test_parse_prefixed_blake3() {
394 let hex = "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262";
395 let prefixed = format!("blake3:{hex}");
396 let id = ID::parse(&prefixed).unwrap();
397
398 assert_eq!(id.algorithm(), HashAlgorithm::Blake3);
399 assert_eq!(id.hex(), hex);
400 assert_eq!(id.as_str(), prefixed);
401 }
402
403 #[test]
404 fn test_from_bytes_deterministic() {
405 let id1 = ID::from_bytes("test_data_foo");
406 let id2 = ID::from_bytes("test_data_foo");
407 let id3 = ID::from_bytes("test_data_bar");
408
409 assert_eq!(id1, id2);
411 assert_ne!(id1, id3);
413
414 assert_eq!(id1.algorithm(), HashAlgorithm::Sha256);
416 }
417
418 #[test]
419 fn test_validation() {
420 assert!(ID::parse("deadbeef").is_err());
422
423 assert!(
425 ID::parse("deadbeef12345678901234567890123456789012345678901234567890123456").is_err()
426 );
427
428 assert!(
430 ID::parse("sha256:deadbeef123456789012345678901234567890123456789012345678901234567g")
431 .is_err()
432 );
433
434 assert!(
436 ID::parse("unknown:deadbeef12345678901234567890123456789012345678901234567890123456")
437 .is_err()
438 );
439
440 assert!(
442 ID::parse("sha256:deadbeef12345678901234567890123456789012345678901234567890123456")
443 .is_ok()
444 );
445 assert!(
446 ID::parse("blake3:deadbeef12345678901234567890123456789012345678901234567890123456")
447 .is_ok()
448 );
449 }
450
451 #[test]
452 fn test_serialization() {
453 let id = ID::from_bytes("test_data_serialization");
454
455 let json = serde_json::to_string(&id).unwrap();
457 let deserialized: ID = serde_json::from_str(&json).unwrap();
458
459 assert_eq!(id, deserialized);
460 assert_eq!(id.algorithm(), deserialized.algorithm());
461 }
462}