Skip to main content

bcp_encoder/
block_writer.rs

1/// TLV field serializer for block bodies.
2///
3/// `BlockWriter` accumulates tag-length-value encoded fields into an
4/// internal byte buffer. It mirrors the field encoding convention from
5/// `bcp_types::fields` but wraps it in a stateful builder that tracks
6/// the buffer and provides a clean `finish()` hand-off.
7///
8/// This is an internal implementation detail of the encoder — it is
9/// not part of the public API. Each block type's serialization calls
10/// into `BlockWriter` to produce the body bytes that get framed by
11/// [`BlockFrame`](bcp_wire::block_frame::BlockFrame).
12///
13/// Wire format per field:
14///
15/// ```text
16/// ┌─────────────────┬──────────────────┬────────────────────────┐
17/// │ field_id (varint)│ wire_type (varint)│ payload (varies)      │
18/// ├─────────────────┼──────────────────┼────────────────────────┤
19/// │                 │ 0 (Varint)       │ value (varint)         │
20/// │                 │ 1 (Bytes)        │ length (varint) + data │
21/// │                 │ 2 (Nested)       │ length (varint) + data │
22/// └─────────────────┴──────────────────┴────────────────────────┘
23/// ```
24pub struct BlockWriter {
25    buf: Vec<u8>,
26}
27
28impl BlockWriter {
29    /// Create a new writer with an empty buffer.
30    #[must_use]
31    pub fn new() -> Self {
32        Self { buf: Vec::new() }
33    }
34
35    /// Create a new writer with a pre-allocated buffer capacity.
36    ///
37    /// Use this when you can estimate the final body size to avoid
38    /// intermediate reallocations.
39    #[must_use]
40    pub fn with_capacity(capacity: usize) -> Self {
41        Self {
42            buf: Vec::with_capacity(capacity),
43        }
44    }
45
46    /// Write a varint field (wire type 0).
47    ///
48    /// Encodes: `field_id (varint) | 0 (varint) | value (varint)`
49    pub fn write_varint_field(&mut self, field_id: u64, value: u64) {
50        bcp_types::fields::encode_varint_field(&mut self.buf, field_id, value);
51    }
52
53    /// Write a bytes field (wire type 1).
54    ///
55    /// Encodes: `field_id (varint) | 1 (varint) | length (varint) | data [length]`
56    ///
57    /// Strings are encoded as bytes fields with UTF-8 content — there is
58    /// no distinct string wire type.
59    pub fn write_bytes_field(&mut self, field_id: u64, value: &[u8]) {
60        bcp_types::fields::encode_bytes_field(&mut self.buf, field_id, value);
61    }
62
63    /// Write a nested field (wire type 2).
64    ///
65    /// Encodes: `field_id (varint) | 2 (varint) | length (varint) | nested [length]`
66    ///
67    /// The `nested` bytes are themselves a sequence of TLV-encoded fields,
68    /// pre-serialized by the caller. This enables recursive structures like
69    /// `FileEntry` children and `DiffHunk` sequences.
70    pub fn write_nested_field(&mut self, field_id: u64, nested: &[u8]) {
71        bcp_types::fields::encode_nested_field(&mut self.buf, field_id, nested);
72    }
73
74    /// Consume the writer and return the accumulated bytes.
75    ///
76    /// After calling `finish()`, the writer is consumed. The returned
77    /// `Vec<u8>` is the complete TLV-encoded body ready to be wrapped
78    /// in a `BlockFrame`.
79    #[must_use]
80    pub fn finish(self) -> Vec<u8> {
81        self.buf
82    }
83}
84
85impl Default for BlockWriter {
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn empty_writer_produces_empty_bytes() {
97        let writer = BlockWriter::new();
98        assert!(writer.finish().is_empty());
99    }
100
101    #[test]
102    fn single_varint_field() {
103        let mut writer = BlockWriter::new();
104        writer.write_varint_field(1, 42);
105        let bytes = writer.finish();
106        assert!(!bytes.is_empty());
107
108        // Verify via bcp-types decode path
109        let (header, n) = bcp_types::fields::decode_field_header(&bytes).unwrap();
110        assert_eq!(header.field_id, 1);
111        assert_eq!(header.wire_type, bcp_types::fields::FieldWireType::Varint);
112        let (val, m) = bcp_types::fields::decode_varint_value(&bytes[n..]).unwrap();
113        assert_eq!(val, 42);
114        assert_eq!(n + m, bytes.len());
115    }
116
117    #[test]
118    fn single_bytes_field() {
119        let mut writer = BlockWriter::new();
120        writer.write_bytes_field(2, b"hello");
121        let bytes = writer.finish();
122
123        let (header, n) = bcp_types::fields::decode_field_header(&bytes).unwrap();
124        assert_eq!(header.field_id, 2);
125        assert_eq!(header.wire_type, bcp_types::fields::FieldWireType::Bytes);
126        let (data, m) = bcp_types::fields::decode_bytes_value(&bytes[n..]).unwrap();
127        assert_eq!(data, b"hello");
128        assert_eq!(n + m, bytes.len());
129    }
130
131    #[test]
132    fn nested_field_roundtrip() {
133        let mut inner = BlockWriter::new();
134        inner.write_varint_field(1, 99);
135        let inner_bytes = inner.finish();
136
137        let mut outer = BlockWriter::new();
138        outer.write_nested_field(3, &inner_bytes);
139        let bytes = outer.finish();
140
141        let (header, n) = bcp_types::fields::decode_field_header(&bytes).unwrap();
142        assert_eq!(header.field_id, 3);
143        assert_eq!(header.wire_type, bcp_types::fields::FieldWireType::Nested);
144        let (nested, m) = bcp_types::fields::decode_bytes_value(&bytes[n..]).unwrap();
145        assert_eq!(n + m, bytes.len());
146
147        // Decode the nested content
148        let (inner_header, k) = bcp_types::fields::decode_field_header(nested).unwrap();
149        assert_eq!(inner_header.field_id, 1);
150        let (val, _) = bcp_types::fields::decode_varint_value(&nested[k..]).unwrap();
151        assert_eq!(val, 99);
152    }
153
154    #[test]
155    fn multiple_fields_sequential() {
156        let mut writer = BlockWriter::new();
157        writer.write_varint_field(1, 7);
158        writer.write_bytes_field(2, b"world");
159        writer.write_varint_field(3, 256);
160        let bytes = writer.finish();
161
162        // Should be decodable as 3 sequential fields
163        let mut cursor = 0;
164
165        let (h, n) = bcp_types::fields::decode_field_header(&bytes[cursor..]).unwrap();
166        cursor += n;
167        assert_eq!(h.field_id, 1);
168        let (v, n) = bcp_types::fields::decode_varint_value(&bytes[cursor..]).unwrap();
169        cursor += n;
170        assert_eq!(v, 7);
171
172        let (h, n) = bcp_types::fields::decode_field_header(&bytes[cursor..]).unwrap();
173        cursor += n;
174        assert_eq!(h.field_id, 2);
175        let (data, n) = bcp_types::fields::decode_bytes_value(&bytes[cursor..]).unwrap();
176        cursor += n;
177        assert_eq!(data, b"world");
178
179        let (h, n) = bcp_types::fields::decode_field_header(&bytes[cursor..]).unwrap();
180        cursor += n;
181        assert_eq!(h.field_id, 3);
182        let (v, n) = bcp_types::fields::decode_varint_value(&bytes[cursor..]).unwrap();
183        cursor += n;
184        assert_eq!(v, 256);
185
186        assert_eq!(cursor, bytes.len());
187    }
188}