Skip to main content

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}