Skip to main content

common/serde/
seq_block.rs

1//! Sequence block value type for block-based ID allocation.
2//!
3//! This module provides the [`SeqBlock`] type, which represents a block of
4//! allocated sequence numbers. It is used by OpenData systems (Log, Vector, etc.)
5//! to implement efficient monotonically increasing ID allocation with crash recovery.
6//!
7//! # Format
8//!
9//! The `SeqBlock` value serializes to 16 bytes:
10//!
11//! ```text
12//! | base_sequence (u64 BE) | block_size (u64 BE) |
13//! ```
14//!
15//! The allocated range is `[base_sequence, base_sequence + block_size)`.
16//!
17//! # Usage
18//!
19//! Each system provides its own key format for storing the SeqBlock record.
20//! For example:
21//! - Log uses key `[0x01, 0x02]` (version + record type)
22//! - Vector uses key `[0x01, 0x08]` (version + record type)
23
24use bytes::{BufMut, Bytes, BytesMut};
25
26use super::DeserializeError;
27
28/// A block of allocated sequence numbers.
29///
30/// Stores the current sequence block allocation as two u64 values:
31/// - `base_sequence`: Starting sequence number of the allocated block
32/// - `block_size`: Number of sequence numbers in the block
33///
34/// The allocated range is `[base_sequence, base_sequence + block_size)`.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct SeqBlock {
37    /// Base sequence number of the allocated block
38    pub base_sequence: u64,
39    /// Size of the allocated block
40    pub block_size: u64,
41}
42
43impl SeqBlock {
44    /// Creates a new SeqBlock value.
45    pub fn new(base_sequence: u64, block_size: u64) -> Self {
46        Self {
47            base_sequence,
48            block_size,
49        }
50    }
51
52    /// Encodes the value to bytes.
53    ///
54    /// Format: `| base_sequence (u64 BE) | block_size (u64 BE) |`
55    pub fn serialize(&self) -> Bytes {
56        let mut buf = BytesMut::with_capacity(16);
57        buf.put_u64(self.base_sequence);
58        buf.put_u64(self.block_size);
59        buf.freeze()
60    }
61
62    /// Decodes a SeqBlock value from bytes.
63    pub fn deserialize(data: &[u8]) -> Result<Self, DeserializeError> {
64        if data.len() < 16 {
65            return Err(DeserializeError {
66                message: format!(
67                    "buffer too short for SeqBlock value: need 16 bytes, got {}",
68                    data.len()
69                ),
70            });
71        }
72
73        let base_sequence = u64::from_be_bytes([
74            data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
75        ]);
76        let block_size = u64::from_be_bytes([
77            data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15],
78        ]);
79
80        Ok(SeqBlock {
81            base_sequence,
82            block_size,
83        })
84    }
85
86    /// Returns the next sequence number after this block.
87    ///
88    /// This is the starting point for the next block allocation.
89    pub fn next_base(&self) -> u64 {
90        self.base_sequence + self.block_size
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn should_serialize_and_deserialize_seq_block() {
100        // given
101        let value = SeqBlock::new(1000, 100);
102
103        // when
104        let serialized = value.serialize();
105        let deserialized = SeqBlock::deserialize(&serialized).unwrap();
106
107        // then
108        assert_eq!(deserialized, value);
109        assert_eq!(serialized.len(), 16);
110    }
111
112    #[test]
113    fn should_calculate_next_base() {
114        // given
115        let value = SeqBlock::new(1000, 100);
116
117        // when
118        let next = value.next_base();
119
120        // then
121        assert_eq!(next, 1100);
122    }
123
124    #[test]
125    fn should_fail_deserialize_when_buffer_too_short() {
126        // given
127        let data = vec![0u8; 15]; // need 16 bytes
128
129        // when
130        let result = SeqBlock::deserialize(&data);
131
132        // then
133        assert!(result.is_err());
134        assert!(
135            result
136                .unwrap_err()
137                .message
138                .contains("buffer too short for SeqBlock value")
139        );
140    }
141
142    #[test]
143    fn should_serialize_in_big_endian() {
144        // given
145        let value = SeqBlock::new(0x0102030405060708, 0x1112131415161718);
146
147        // when
148        let serialized = value.serialize();
149
150        // then
151        assert_eq!(
152            serialized.as_ref(),
153            &[
154                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // base_sequence
155                0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // block_size
156            ]
157        );
158    }
159
160    #[test]
161    fn should_handle_zero_values() {
162        // given
163        let value = SeqBlock::new(0, 0);
164
165        // when
166        let serialized = value.serialize();
167        let deserialized = SeqBlock::deserialize(&serialized).unwrap();
168
169        // then
170        assert_eq!(deserialized.base_sequence, 0);
171        assert_eq!(deserialized.block_size, 0);
172        assert_eq!(deserialized.next_base(), 0);
173    }
174
175    #[test]
176    fn should_handle_max_values() {
177        // given
178        let value = SeqBlock::new(u64::MAX, u64::MAX);
179
180        // when
181        let serialized = value.serialize();
182        let deserialized = SeqBlock::deserialize(&serialized).unwrap();
183
184        // then
185        assert_eq!(deserialized.base_sequence, u64::MAX);
186        assert_eq!(deserialized.block_size, u64::MAX);
187    }
188}