1use std::marker::PhantomData;
40
41use serde::Serialize;
42use serde::de::DeserializeOwned;
43
44use noxu_db::DatabaseEntry;
45
46use crate::Result;
47use crate::entry_binding::EntryBinding;
48use crate::serial::simple_serial;
49
50pub const SERDE_BINDING_MAGIC: u8 = 0xCB;
54
55pub const SERDE_BINDING_VERSION: u8 = 0x01;
58
59pub const SERDE_BINDING_HEADER_LEN: usize = 2;
61
62pub struct SerdeBinding<T> {
116 _phantom: PhantomData<T>,
117}
118
119impl<T> SerdeBinding<T> {
120 pub fn new() -> Self {
122 Self { _phantom: PhantomData }
123 }
124}
125
126impl<T> Default for SerdeBinding<T> {
127 fn default() -> Self {
128 Self::new()
129 }
130}
131
132impl<T> Clone for SerdeBinding<T> {
133 fn clone(&self) -> Self {
134 Self::new()
135 }
136}
137
138impl<T> std::fmt::Debug for SerdeBinding<T> {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 f.debug_struct("SerdeBinding")
141 .field("type", &std::any::type_name::<T>())
142 .finish()
143 }
144}
145
146impl<T: Serialize + DeserializeOwned> EntryBinding<T> for SerdeBinding<T> {
147 fn entry_to_object(&self, entry: &DatabaseEntry) -> Result<T> {
148 let data = entry.data();
149 if data.len() < SERDE_BINDING_HEADER_LEN {
150 return Err(crate::BindError::VersionMismatch {
151 expected_magic: SERDE_BINDING_MAGIC,
152 expected_version: SERDE_BINDING_VERSION,
153 found_magic: data.first().copied().unwrap_or(0),
154 found_version: data.get(1).copied().unwrap_or(0),
155 });
156 }
157 if data[0] != SERDE_BINDING_MAGIC || data[1] != SERDE_BINDING_VERSION {
158 return Err(crate::BindError::VersionMismatch {
159 expected_magic: SERDE_BINDING_MAGIC,
160 expected_version: SERDE_BINDING_VERSION,
161 found_magic: data[0],
162 found_version: data[1],
163 });
164 }
165 simple_serial::from_bytes(&data[SERDE_BINDING_HEADER_LEN..])
166 }
167
168 fn object_to_entry(
169 &self,
170 object: &T,
171 entry: &mut DatabaseEntry,
172 ) -> Result<()> {
173 let body = simple_serial::to_bytes(object)?;
174 let mut bytes =
175 Vec::with_capacity(body.len() + SERDE_BINDING_HEADER_LEN);
176 bytes.push(SERDE_BINDING_MAGIC);
177 bytes.push(SERDE_BINDING_VERSION);
178 bytes.extend_from_slice(&body);
179 entry.set_data_vec(bytes);
180 Ok(())
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use serde::{Deserialize, Serialize};
188
189 #[test]
190 fn test_u32_round_trip() {
191 let binding = SerdeBinding::<u32>::new();
192 let mut entry = DatabaseEntry::new();
193 binding.object_to_entry(&42u32, &mut entry).unwrap();
194 assert_eq!(binding.entry_to_object(&entry).unwrap(), 42u32);
195 }
196
197 #[test]
198 fn test_string_round_trip() {
199 let binding = SerdeBinding::<String>::new();
200 let mut entry = DatabaseEntry::new();
201 let s = "hello world".to_string();
202 binding.object_to_entry(&s, &mut entry).unwrap();
203 assert_eq!(binding.entry_to_object(&entry).unwrap(), s);
204 }
205
206 #[derive(Debug, PartialEq, Serialize, Deserialize)]
207 struct TestRecord {
208 id: u64,
209 name: String,
210 active: bool,
211 }
212
213 #[test]
214 fn test_struct_round_trip() {
215 let binding = SerdeBinding::<TestRecord>::new();
216 let record =
217 TestRecord { id: 12345, name: "test".to_string(), active: true };
218 let mut entry = DatabaseEntry::new();
219 binding.object_to_entry(&record, &mut entry).unwrap();
220 assert_eq!(binding.entry_to_object(&entry).unwrap(), record);
221 }
222
223 #[test]
224 fn test_vec_round_trip() {
225 let binding = SerdeBinding::<Vec<u32>>::new();
226 let v = vec![1, 2, 3, 4, 5];
227 let mut entry = DatabaseEntry::new();
228 binding.object_to_entry(&v, &mut entry).unwrap();
229 assert_eq!(binding.entry_to_object(&entry).unwrap(), v);
230 }
231
232 #[test]
233 fn test_option_round_trip() {
234 let binding = SerdeBinding::<Option<String>>::new();
235 let mut entry = DatabaseEntry::new();
236
237 binding.object_to_entry(&Some("yes".to_string()), &mut entry).unwrap();
238 assert_eq!(
239 binding.entry_to_object(&entry).unwrap(),
240 Some("yes".to_string())
241 );
242
243 binding.object_to_entry(&None, &mut entry).unwrap();
244 assert_eq!(binding.entry_to_object(&entry).unwrap(), None);
245 }
246
247 #[derive(Debug, PartialEq, Serialize, Deserialize)]
248 enum Status {
249 Active,
250 Inactive,
251 Pending(String),
252 }
253
254 #[test]
255 fn test_enum_round_trip() {
256 let binding = SerdeBinding::<Status>::new();
257 let mut entry = DatabaseEntry::new();
258
259 binding.object_to_entry(&Status::Active, &mut entry).unwrap();
260 assert_eq!(binding.entry_to_object(&entry).unwrap(), Status::Active);
261
262 binding
263 .object_to_entry(&Status::Pending("review".to_string()), &mut entry)
264 .unwrap();
265 assert_eq!(
266 binding.entry_to_object(&entry).unwrap(),
267 Status::Pending("review".to_string())
268 );
269 }
270
271 #[test]
272 fn test_default() {
273 let binding = SerdeBinding::<u32>::default();
274 let mut entry = DatabaseEntry::new();
275 binding.object_to_entry(&7u32, &mut entry).unwrap();
276 assert_eq!(binding.entry_to_object(&entry).unwrap(), 7u32);
277 }
278
279 #[test]
280 fn test_clone() {
281 let binding = SerdeBinding::<u32>::new();
282 let cloned = binding.clone();
283 let mut entry = DatabaseEntry::new();
284 binding.object_to_entry(&99u32, &mut entry).unwrap();
285 assert_eq!(cloned.entry_to_object(&entry).unwrap(), 99u32);
286 }
287
288 #[test]
289 fn test_debug() {
290 let binding = SerdeBinding::<u32>::new();
291 let debug = format!("{:?}", binding);
292 assert!(debug.contains("SerdeBinding"));
293 }
294
295 #[test]
296 fn test_empty_entry_error() {
297 let binding = SerdeBinding::<u32>::new();
298 let entry = DatabaseEntry::new();
299 assert!(binding.entry_to_object(&entry).is_err());
301 }
302
303 #[derive(Debug, PartialEq, Serialize, Deserialize)]
304 struct Nested {
305 inner: TestRecord,
306 tags: Vec<String>,
307 }
308
309 #[test]
310 fn test_nested_struct_round_trip() {
311 let binding = SerdeBinding::<Nested>::new();
312 let nested = Nested {
313 inner: TestRecord {
314 id: 1,
315 name: "nested".to_string(),
316 active: false,
317 },
318 tags: vec!["a".to_string(), "b".to_string()],
319 };
320 let mut entry = DatabaseEntry::new();
321 binding.object_to_entry(&nested, &mut entry).unwrap();
322 assert_eq!(binding.entry_to_object(&entry).unwrap(), nested);
323 }
324
325 #[test]
326 fn test_tuple_round_trip() {
327 let binding = SerdeBinding::<(u32, String, bool)>::new();
328 let val = (42u32, "hello".to_string(), true);
329 let mut entry = DatabaseEntry::new();
330 binding.object_to_entry(&val, &mut entry).unwrap();
331 assert_eq!(binding.entry_to_object(&entry).unwrap(), val);
332 }
333
334 #[test]
335 fn test_entry_data_is_set() {
336 let binding = SerdeBinding::<u32>::new();
337 let mut entry = DatabaseEntry::new();
338 assert!(entry.is_empty());
339 binding.object_to_entry(&42u32, &mut entry).unwrap();
340 assert!(!entry.is_empty());
341 assert!(entry.get_data().is_some());
342 }
343
344 #[test]
350 fn test_encoded_payload_starts_with_version_header() {
351 let binding = SerdeBinding::<u32>::new();
352 let mut entry = DatabaseEntry::new();
353 binding.object_to_entry(&42u32, &mut entry).unwrap();
354
355 let bytes = entry.get_data().unwrap();
356 assert!(
357 bytes.len() >= SERDE_BINDING_HEADER_LEN,
358 "encoded entry must include the 2-byte header",
359 );
360 assert_eq!(bytes[0], SERDE_BINDING_MAGIC);
361 assert_eq!(bytes[1], SERDE_BINDING_VERSION);
362 assert_eq!(&bytes[2..], &[0, 0, 0, 42]);
364 }
365
366 #[test]
370 fn test_decode_unprefixed_payload_returns_version_mismatch() {
371 let entry = DatabaseEntry::from_bytes(&[0, 0, 0, 42]);
373 let binding = SerdeBinding::<u32>::new();
374
375 let err = binding
376 .entry_to_object(&entry)
377 .expect_err("unprefixed payload must fail to decode");
378 match err {
379 crate::BindError::VersionMismatch {
380 expected_magic,
381 expected_version,
382 found_magic,
383 found_version,
384 } => {
385 assert_eq!(expected_magic, SERDE_BINDING_MAGIC);
386 assert_eq!(expected_version, SERDE_BINDING_VERSION);
387 assert_eq!(found_magic, 0x00);
390 assert_eq!(found_version, 0x00);
391 }
392 other => panic!("expected VersionMismatch, got {:?}", other),
393 }
394 }
395
396 #[test]
399 fn test_decode_short_payload_returns_version_mismatch() {
400 let binding = SerdeBinding::<u32>::new();
401
402 for short in &[&[][..], &[SERDE_BINDING_MAGIC][..]] {
403 let entry = DatabaseEntry::from_bytes(short);
404 let err = binding
405 .entry_to_object(&entry)
406 .expect_err("short payload must fail to decode");
407 assert!(
408 matches!(err, crate::BindError::VersionMismatch { .. }),
409 "short payload (len={}) must fail with VersionMismatch, got {:?}",
410 short.len(),
411 err,
412 );
413 }
414 }
415
416 #[test]
420 fn test_decode_wrong_version_returns_version_mismatch() {
421 let mut bytes = vec![SERDE_BINDING_MAGIC, 0xFF];
422 bytes.extend_from_slice(&42u32.to_be_bytes());
423 let entry = DatabaseEntry::from_bytes(&bytes);
424 let binding = SerdeBinding::<u32>::new();
425
426 let err = binding
427 .entry_to_object(&entry)
428 .expect_err("wrong-version payload must fail to decode");
429 match err {
430 crate::BindError::VersionMismatch {
431 found_magic,
432 found_version,
433 ..
434 } => {
435 assert_eq!(found_magic, SERDE_BINDING_MAGIC);
436 assert_eq!(found_version, 0xFF);
437 }
438 other => panic!("expected VersionMismatch, got {:?}", other),
439 }
440 }
441
442 #[test]
445 fn test_version_mismatch_display() {
446 let err = crate::BindError::VersionMismatch {
447 expected_magic: 0xCB,
448 expected_version: 0x01,
449 found_magic: 0x00,
450 found_version: 0x00,
451 };
452 let s = err.to_string();
453 assert!(s.contains("0xCB"), "display must include expected magic: {s}");
454 assert!(
455 s.contains("0x01"),
456 "display must include expected version: {s}"
457 );
458 assert!(
459 s.contains("version mismatch"),
460 "display must name the failure: {s}"
461 );
462 }
463}