rucksack_db/db/
versioned.rs

1use anyhow::{anyhow, Result};
2use bincode::{Decode, Encode};
3use serde::{Deserialize, Serialize};
4
5use rucksack_lib::util;
6
7use crate::records;
8
9#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, Encode, Decode)]
10pub struct VersionedDB {
11    bytes: Vec<u8>,
12    version: String,
13}
14
15impl VersionedDB {
16    pub fn new(bytes: Vec<u8>, version: String) -> Result<VersionedDB> {
17        // Validate version format immediately
18        versions::SemVer::new(&version)
19            .ok_or_else(|| anyhow!("invalid version format '{}'", version))?;
20
21        Ok(VersionedDB { bytes, version })
22    }
23
24    pub fn deserialise(bytes: Vec<u8>) -> Result<VersionedDB> {
25        log::debug!(operation = "deserialise"; "Creating versioned DB from previously serialised versioned DB");
26        match bincode::decode_from_slice::<VersionedDB, _>(bytes.as_ref(), util::bincode_cfg()) {
27            Ok((result, _len)) => {
28                log::trace!(version = result.version.as_str(), operation = "deserialise"; "Deserialised versioned DB bytes");
29                Ok(result)
30            }
31            Err(e) => {
32                let msg = format!("couldn't deserialise versioned database file: {e:?}");
33                log::error!(error = e.to_string().as_str(), operation = "deserialise"; "{}", msg);
34                Err(anyhow!(msg))
35            }
36        }
37    }
38
39    pub fn from_bytes(bytes: Vec<u8>) -> Result<VersionedDB> {
40        log::debug!(operation = "init"; "Initialising versioned DB with encoded hashmap");
41        VersionedDB::new(bytes, records::version().to_string())
42    }
43
44    pub fn bytes(&self) -> Vec<u8> {
45        self.bytes.clone()
46    }
47
48    pub fn hash(&self) -> u32 {
49        crc32fast::hash(self.bytes.as_ref())
50    }
51
52    pub fn serialise(&self) -> Result<Vec<u8>> {
53        log::debug!(operation = "serialise"; "Serialising versioned DB");
54        match bincode::encode_to_vec(self, util::bincode_cfg()) {
55            Ok(bytes) => Ok(bytes),
56            Err(e) => {
57                let msg = format!("couldn't serialise versioned database ({e})");
58                log::error!(error = e.to_string().as_str(), operation = "serialise"; "{}", msg);
59                Err(anyhow!("{}", msg))
60            }
61        }
62    }
63
64    pub fn version(&self) -> versions::SemVer {
65        // SAFETY: Version string is validated in constructor
66        versions::SemVer::new(self.version.as_str()).expect("version validated in constructor")
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn test_new_basic() {
76        let vsn_db = VersionedDB::new(vec![1, 2, 3], "1.0.0".to_string()).unwrap();
77        assert_eq!(vsn_db.bytes(), vec![1, 2, 3]);
78        assert_eq!(vsn_db.version(), versions::SemVer::new("1.0.0").unwrap());
79    }
80
81    #[test]
82    fn test_new_empty_bytes() {
83        let vsn_db = VersionedDB::new(vec![], "0.1.0".to_string()).unwrap();
84        assert_eq!(vsn_db.bytes(), vec![]);
85        assert_eq!(vsn_db.version(), versions::SemVer::new("0.1.0").unwrap());
86    }
87
88    #[test]
89    fn test_new_large_bytes() {
90        let large_vec = vec![42u8; 10000];
91        let vsn_db = VersionedDB::new(large_vec.clone(), "2.0.0".to_string()).unwrap();
92        assert_eq!(vsn_db.bytes(), large_vec);
93    }
94
95    #[test]
96    fn test_from_bytes() {
97        let bytes = vec![5, 10, 15];
98        let vsn_db = VersionedDB::from_bytes(bytes.clone()).unwrap();
99        assert_eq!(vsn_db.bytes(), bytes);
100        // Should use current records version - just verify it parses
101        let _version = vsn_db.version();
102    }
103
104    #[test]
105    fn test_bytes_getter() {
106        let original = vec![1, 2, 3, 4, 5];
107        let vsn_db = VersionedDB::new(original.clone(), "1.0.0".to_string()).unwrap();
108        let retrieved = vsn_db.bytes();
109        assert_eq!(retrieved, original);
110    }
111
112    #[test]
113    fn test_hash_consistent() {
114        let vsn_db = VersionedDB::new(vec![1, 2, 3], "1.0.0".to_string()).unwrap();
115        let hash1 = vsn_db.hash();
116        let hash2 = vsn_db.hash();
117        assert_eq!(hash1, hash2, "Hash should be consistent");
118    }
119
120    #[test]
121    fn test_hash_different_for_different_bytes() {
122        let vsn_db1 = VersionedDB::new(vec![1, 2, 3], "1.0.0".to_string()).unwrap();
123        let vsn_db2 = VersionedDB::new(vec![4, 5, 6], "1.0.0".to_string()).unwrap();
124        assert_ne!(vsn_db1.hash(), vsn_db2.hash());
125    }
126
127    #[test]
128    fn test_hash_empty_bytes() {
129        let vsn_db = VersionedDB::new(vec![], "1.0.0".to_string()).unwrap();
130        let hash = vsn_db.hash();
131        assert_eq!(hash, 0); // CRC32 of empty data is 0
132    }
133
134    #[test]
135    fn test_hash_known_value() {
136        let vsn_db = VersionedDB::new(vec![2, 4, 16], "1.2.3".to_string()).unwrap();
137        assert_eq!(vsn_db.hash(), 2233391132);
138    }
139
140    #[test]
141    fn test_version_parsing() {
142        let vsn_db = VersionedDB::new(vec![1], "3.14.159".to_string()).unwrap();
143        let version = vsn_db.version();
144        assert_eq!(version.major, 3);
145        assert_eq!(version.minor, 14);
146        assert_eq!(version.patch, 159);
147    }
148
149    #[test]
150    fn test_version_with_prerelease() {
151        let vsn_db = VersionedDB::new(vec![1], "2.0.0-alpha.1".to_string()).unwrap();
152        let version = vsn_db.version();
153        assert_eq!(version.major, 2);
154        assert_eq!(version.minor, 0);
155        assert_eq!(version.patch, 0);
156    }
157
158    #[test]
159    fn test_version_comparisons() {
160        let vsn_db = VersionedDB::new(vec![1, 2], "1.5.0".to_string()).unwrap();
161        assert!(vsn_db.version() > versions::SemVer::new("1.4.9").unwrap());
162        assert!(vsn_db.version() < versions::SemVer::new("1.5.1").unwrap());
163        assert_eq!(vsn_db.version(), versions::SemVer::new("1.5.0").unwrap());
164    }
165
166    #[test]
167    fn test_serialise_basic() {
168        let vsn_db = VersionedDB::new(vec![10, 20], "1.0.0".to_string()).unwrap();
169        let result = vsn_db.serialise();
170        assert!(result.is_ok());
171        let bytes = result.unwrap();
172        assert!(!bytes.is_empty());
173    }
174
175    #[test]
176    fn test_serialise_empty() {
177        let vsn_db = VersionedDB::new(vec![], "0.0.1".to_string()).unwrap();
178        let result = vsn_db.serialise();
179        assert!(result.is_ok());
180    }
181
182    #[test]
183    fn test_deserialise_basic() {
184        let original = VersionedDB::new(vec![1, 2, 3], "1.2.3".to_string()).unwrap();
185        let serialised = original.serialise().unwrap();
186        let deserialised = VersionedDB::deserialise(serialised).unwrap();
187        assert_eq!(deserialised.bytes(), original.bytes());
188        assert_eq!(deserialised.version(), original.version());
189    }
190
191    #[test]
192    fn test_deserialise_invalid_bytes() {
193        let invalid_bytes = vec![255, 255, 255, 1, 2, 3];
194        let result = VersionedDB::deserialise(invalid_bytes);
195        assert!(result.is_err(), "Should fail to deserialise invalid bytes");
196    }
197
198    #[test]
199    fn test_deserialise_empty_bytes() {
200        let result = VersionedDB::deserialise(vec![]);
201        assert!(result.is_err(), "Should fail to deserialise empty bytes");
202    }
203
204    #[test]
205    fn test_deserialise_truncated_bytes() {
206        let original = VersionedDB::new(vec![1, 2, 3], "1.0.0".to_string()).unwrap();
207        let serialised = original.serialise().unwrap();
208        // Truncate to cause a deserialization error
209        let truncated = &serialised[..serialised.len() / 2];
210        let result = VersionedDB::deserialise(truncated.to_vec());
211        assert!(result.is_err(), "Truncated data should fail to deserialise");
212        if let Err(e) = result {
213            assert!(e.to_string().contains("deserialise"));
214        }
215    }
216
217    #[test]
218    fn test_serialise_deserialise_roundtrip() {
219        let original = VersionedDB::new(vec![5, 10, 15, 20], "2.1.0".to_string()).unwrap();
220        let serialised = original.serialise().unwrap();
221        let deserialised = VersionedDB::deserialise(serialised).unwrap();
222
223        assert_eq!(original.bytes(), deserialised.bytes());
224        assert_eq!(original.version(), deserialised.version());
225        assert_eq!(original.hash(), deserialised.hash());
226    }
227
228    #[test]
229    fn test_serialise_deserialise_large_data() {
230        let large_data = vec![123u8; 5000];
231        let original = VersionedDB::new(large_data, "3.0.0".to_string()).unwrap();
232        let serialised = original.serialise().unwrap();
233        let deserialised = VersionedDB::deserialise(serialised).unwrap();
234
235        assert_eq!(original.bytes(), deserialised.bytes());
236        assert_eq!(original.hash(), deserialised.hash());
237    }
238
239    #[test]
240    fn test_clone() {
241        let original = VersionedDB::new(vec![1, 2, 3], "1.0.0".to_string()).unwrap();
242        let cloned = original.clone();
243        assert_eq!(original.bytes(), cloned.bytes());
244        assert_eq!(original.version(), cloned.version());
245        assert_eq!(original.hash(), cloned.hash());
246    }
247
248    #[test]
249    fn test_equality() {
250        let db1 = VersionedDB::new(vec![1, 2, 3], "1.0.0".to_string()).unwrap();
251        let db2 = VersionedDB::new(vec![1, 2, 3], "1.0.0".to_string()).unwrap();
252        let db3 = VersionedDB::new(vec![1, 2, 3], "1.0.1".to_string()).unwrap();
253        let db4 = VersionedDB::new(vec![1, 2, 4], "1.0.0".to_string()).unwrap();
254
255        assert_eq!(db1, db2);
256        assert_ne!(db1, db3); // Different version
257        assert_ne!(db1, db4); // Different bytes
258    }
259
260    #[test]
261    fn db_bytes() {
262        let vsn_db = VersionedDB::new(vec![2, 4, 16], "1.2.3".to_string()).unwrap();
263        assert!(vsn_db.version() > versions::SemVer::new("0.3.0").unwrap());
264        assert_eq!(vsn_db.hash(), 2233391132);
265        assert_eq!(vsn_db.version(), versions::SemVer::new("1.2.3").unwrap());
266        assert!(vsn_db.version() < versions::SemVer::new("2.3.0").unwrap());
267        let encoded = vsn_db.serialise().unwrap();
268        let expected = vec![
269            3, 0, 0, 0, 0, 0, 0, 0, 2, 4, 16, 5, 0, 0, 0, 0, 0, 0, 0, 49, 46, 50, 46, 51,
270        ];
271        assert_eq!(encoded, expected);
272    }
273}