timeseries_table_core/coverage/serde.rs
1//! Serialization and deserialization of coverage bitmaps.
2//!
3//! This module provides helpers to convert [`Coverage`] instances to and from
4//! byte buffers using the RoaringBitmap binary format. This is used for
5//! persisting coverage snapshots to disk and loading them back.
6//!
7//! # Serialization Format
8//!
9//! Coverage data is serialized to bytes using the RoaringBitmap binary format
10//! (portable across platforms). The byte format is opaque and should not be
11//! interpreted directly; always use [`coverage_from_bytes`] to deserialize.
12//!
13//! # Example
14//!
15//! ```ignore
16//! use timeseries_table_core::coverage::Coverage;
17//! use timeseries_table_core::coverage::serde::{coverage_to_bytes, coverage_from_bytes};
18//!
19//! let cov = Coverage::from_iter(vec![1u32, 2, 3]);
20//! let bytes = coverage_to_bytes(&cov)?;
21//! let restored = coverage_from_bytes(&bytes)?;
22//! assert_eq!(cov.cardinality(), restored.cardinality());
23//! # Ok::<(), Box<dyn std::error::Error>>(())
24//! ```
25
26use std::io::Cursor;
27
28use roaring::RoaringBitmap;
29use snafu::{ResultExt, Snafu};
30
31use crate::coverage::Coverage;
32
33/// Errors that can occur during coverage serialization or deserialization.
34///
35/// These errors indicate I/O failures when reading or writing the RoaringBitmap
36/// binary format. Callers should handle these gracefully and may retry or fall back
37/// to recovering coverage from the source data.
38#[derive(Debug, Snafu)]
39pub enum CoverageSerdeError {
40 /// I/O error during serialization of a coverage bitmap.
41 #[snafu(display("Failed to serialize roaring bitmap: {source}"))]
42 Serialize {
43 /// The underlying I/O error.
44 source: std::io::Error,
45 },
46
47 /// I/O error during deserialization of a coverage bitmap.
48 #[snafu(display("Failed to deserialize roaring bitmap: {source}"))]
49 Deserialize {
50 /// The underlying I/O error.
51 source: std::io::Error,
52 },
53}
54
55/// Serialize a coverage bitmap to a byte vector.
56///
57/// Converts the given [`Coverage`] instance to its RoaringBitmap binary representation,
58/// which can be written to disk or transmitted over the network.
59///
60/// # Arguments
61///
62/// * `cov` - The coverage instance to serialize.
63///
64/// # Returns
65///
66/// A vector of bytes in RoaringBitmap binary format, or an error if serialization fails.
67///
68/// # Errors
69///
70/// Returns [`CoverageSerdeError::Serialize`] if an I/O error occurs during serialization.
71pub fn coverage_to_bytes(cov: &Coverage) -> Result<Vec<u8>, CoverageSerdeError> {
72 let mut out = Vec::new();
73 {
74 let mut w = Cursor::new(&mut out);
75 cov.present()
76 .serialize_into(&mut w)
77 .context(SerializeSnafu)?;
78 }
79 Ok(out)
80}
81
82/// Deserialize a coverage bitmap from bytes.
83///
84/// Reconstructs a [`Coverage`] instance from bytes previously written by [`coverage_to_bytes`].
85/// The byte format is the RoaringBitmap portable binary representation.
86///
87/// # Arguments
88///
89/// * `bytes` - A byte slice in RoaringBitmap binary format.
90///
91/// # Returns
92///
93/// A reconstructed [`Coverage`] instance, or an error if deserialization fails.
94///
95/// # Errors
96///
97/// Returns [`CoverageSerdeError::Deserialize`] if an I/O error occurs during deserialization
98/// or if the byte sequence is not a valid RoaringBitmap.
99pub fn coverage_from_bytes(bytes: &[u8]) -> Result<Coverage, CoverageSerdeError> {
100 let mut r = Cursor::new(bytes);
101 let bm = RoaringBitmap::deserialize_from(&mut r).context(DeserializeSnafu)?;
102 Ok(Coverage::from_bitmap(bm))
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn round_trip_empty_and_non_empty() {
111 // Empty coverage
112 let cov_empty = Coverage::empty();
113 let bytes = coverage_to_bytes(&cov_empty).expect("serialize empty");
114 let restored = coverage_from_bytes(&bytes).expect("deserialize empty");
115 assert_eq!(cov_empty.cardinality(), restored.cardinality());
116
117 // Non-empty coverage
118 let cov = Coverage::from_iter(vec![1u32, 2, 3, 100]);
119 let bytes = coverage_to_bytes(&cov).expect("serialize non-empty");
120 let restored = coverage_from_bytes(&bytes).expect("deserialize non-empty");
121 assert_eq!(cov.present(), restored.present());
122 }
123
124 #[test]
125 fn deserialize_rejects_invalid_bytes() {
126 let bad = b"not a roaring bitmap";
127 let err = coverage_from_bytes(bad).unwrap_err();
128 match err {
129 CoverageSerdeError::Deserialize { .. } => {}
130 _ => panic!("expected deserialize error"),
131 }
132 }
133
134 #[test]
135 fn serialize_reports_io_error() {
136 // Force an I/O error by using a writer that always errors.
137 struct FailingWriter;
138 impl std::io::Write for FailingWriter {
139 fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
140 Err(std::io::Error::other("fail"))
141 }
142 fn flush(&mut self) -> std::io::Result<()> {
143 Ok(())
144 }
145 }
146
147 let cov = Coverage::from_iter(vec![1u32]);
148
149 // Reimplement minimal logic to inject failing writer
150 let err = {
151 let mut w = FailingWriter;
152 cov.present()
153 .serialize_into(&mut w)
154 .map_err(|e| CoverageSerdeError::Serialize { source: e })
155 .unwrap_err()
156 };
157
158 match err {
159 CoverageSerdeError::Serialize { .. } => {}
160 _ => panic!("expected serialize error"),
161 }
162 }
163}