Skip to main content

backbone_bucket/domain/entity/
content_hash.ext.rs

1//! The extension of `content_hash.rs` (hand-written; ADR-0031).
2
3#[allow(unused_imports)]
4use super::*;
5
6impl ContentHash {
7
8    // ==========================================================
9    // DDD Entity Methods
10    // ==========================================================
11
12    /// Increment reference count
13    pub fn increment_reference(&mut self) -> Result<(), DedupError> {
14        self.reference_count += 1;
15        self.last_referenced_at = Utc::now();
16        self.metadata.touch();
17        Ok(())
18    }
19
20    /// Decrement reference count (min 0), return true if now 0 (eligible for deletion)
21    pub fn decrement_reference(&mut self) -> Result<bool, DedupError> {
22        self.reference_count = (self.reference_count - 1).max(0);
23        self.metadata.touch();
24        Ok(self.reference_count == 0)
25    }
26
27    /// Check if content can be deleted (ref_count == 0)
28    pub fn can_delete(&self) -> bool {
29        self.reference_count == 0
30    }
31
32    /// Check if content has no references
33    pub fn is_unused(&self) -> bool {
34        self.reference_count == 0
35    }
36
37    /// Calculate storage saved by deduplication: size_bytes * (reference_count - 1)
38    pub fn storage_saved(&self) -> i64 {
39        self.size_bytes * (self.reference_count - 1).max(0) as i64
40    }
41
42    /// Get age in days since first upload
43    pub fn age_days(&self) -> i32 {
44        (Utc::now() - self.first_uploaded_at).num_days() as i32
45    }
46
47    /// Days since last reference
48    pub fn days_since_last_reference(&self) -> i32 {
49        (Utc::now() - self.last_referenced_at).num_days() as i32
50    }
51
52    /// Check all business invariants
53    pub fn check_invariants(&self) -> Result<(), Vec<&'static str>> {
54        let mut errors = Vec::new();
55        if self.reference_count < 0 {
56            errors.push("reference_count must be >= 0");
57        }
58        if self.hash.is_empty() || self.hash.contains("..") {
59            errors.push("hash must not be empty and must not contain '..'");
60        }
61        if self.size_bytes <= 0 {
62            errors.push("size_bytes must be > 0");
63        }
64        if errors.is_empty() { Ok(()) } else { Err(errors) }
65    }
66}