Skip to main content

qubit_io/stream/
leb128_writer.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2026 Haixing Hu.
4 *
5 *    SPDX-License-Identifier: Apache-2.0
6 *
7 *    Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10
11use std::io::{
12    Result,
13    Seek,
14    SeekFrom,
15    Write,
16};
17
18use crate::WriteExt;
19use crate::codec::{
20    Leb128Codec,
21    NonStrict,
22};
23
24/// Writer wrapper for canonical LEB128 integers.
25///
26/// # Target-width integers
27///
28/// `usize` and `isize` methods use the current Rust target's pointer width.
29/// Prefer fixed-width integer methods such as `write_u64` or `write_i64` for
30/// persistent files and cross-platform protocols.
31pub struct Leb128Writer<W> {
32    inner: W,
33    buffer: [u8; 19],
34}
35
36impl<W> Leb128Writer<W> {
37    /// Creates a LEB128 writer.
38    #[must_use]
39    #[inline]
40    pub const fn new(inner: W) -> Self {
41        Self { inner, buffer: [0; 19] }
42    }
43
44    /// Returns a shared reference to the underlying writer.
45    #[must_use]
46    #[inline]
47    pub const fn get_ref(&self) -> &W {
48        &self.inner
49    }
50
51    /// Returns an exclusive reference to the underlying writer.
52    #[must_use]
53    #[inline]
54    pub fn get_mut(&mut self) -> &mut W {
55        &mut self.inner
56    }
57
58    /// Consumes this wrapper and returns the underlying writer.
59    #[must_use]
60    #[inline]
61    pub fn into_inner(self) -> W {
62        self.inner
63    }
64}
65
66macro_rules! impl_write_value {
67    ($method:ident, $ty:ty, $doc:literal) => {
68        #[doc = $doc]
69        #[inline]
70        pub fn $method(&mut self, value: $ty) -> Result<()> {
71            type Codec = Leb128Codec<$ty, NonStrict>;
72
73            self.write_leb128::<$ty, { Codec::REQUIRED_MIN_BUFFER_LEN }, _>(value, |bytes, value| unsafe {
74                Codec::write_unchecked(bytes, 0, value)
75            })
76        }
77    };
78}
79
80impl<W> Leb128Writer<W>
81where
82    W: Write,
83{
84    #[inline]
85    fn write_leb128<T, const N: usize, F>(&mut self, value: T, encode: F) -> Result<()>
86    where
87        F: FnOnce(&mut [u8; 19], T) -> usize,
88    {
89        let len = encode(&mut self.buffer, value);
90        // SAFETY: The codec returns a length within the fixed internal buffer.
91        unsafe { self.inner.write_all_unchecked(&self.buffer, 0, len) }
92    }
93
94    impl_write_value!(write_u8, u8, "Writes an unsigned LEB128 `u8`.");
95    impl_write_value!(write_u16, u16, "Writes an unsigned LEB128 `u16`.");
96    impl_write_value!(write_u32, u32, "Writes an unsigned LEB128 `u32`.");
97    impl_write_value!(write_u64, u64, "Writes an unsigned LEB128 `u64`.");
98    impl_write_value!(write_u128, u128, "Writes an unsigned LEB128 `u128`.");
99    impl_write_value!(write_usize, usize, "Writes an unsigned LEB128 `usize`.");
100    impl_write_value!(write_i8, i8, "Writes a signed LEB128 `i8`.");
101    impl_write_value!(write_i16, i16, "Writes a signed LEB128 `i16`.");
102    impl_write_value!(write_i32, i32, "Writes a signed LEB128 `i32`.");
103    impl_write_value!(write_i64, i64, "Writes a signed LEB128 `i64`.");
104    impl_write_value!(write_i128, i128, "Writes a signed LEB128 `i128`.");
105    impl_write_value!(write_isize, isize, "Writes a signed LEB128 `isize`.");
106
107    /// Writes a UTF-8 string prefixed by an unsigned LEB128 byte length.
108    ///
109    /// The length prefix is encoded as `usize`, so this format is target-width
110    /// dependent. Prefer a fixed-width length prefix for persistent files and
111    /// cross-platform protocols.
112    ///
113    /// # Parameters
114    ///
115    /// - `value`: String slice to write.
116    ///
117    /// # Errors
118    ///
119    /// Returns an I/O error from the underlying writer.
120    #[inline]
121    pub fn write_utf8_string(&mut self, value: &str) -> Result<()> {
122        self.write_usize(value.len())?;
123        let bytes = value.as_bytes();
124        // SAFETY: The range covers the full byte slice produced by `str::as_bytes`.
125        unsafe { self.inner.write_all_unchecked(bytes, 0, bytes.len()) }
126    }
127}
128
129impl<W> Write for Leb128Writer<W>
130where
131    W: Write,
132{
133    /// Writes bytes to the wrapped writer.
134    ///
135    /// # Parameters
136    ///
137    /// - `buffer`: Source bytes to write.
138    ///
139    /// # Returns
140    ///
141    /// Returns the number of bytes written.
142    ///
143    /// # Errors
144    ///
145    /// Returns the I/O error reported by the wrapped writer.
146    #[inline]
147    fn write(&mut self, buffer: &[u8]) -> Result<usize> {
148        self.inner.write(buffer)
149    }
150
151    /// Flushes the wrapped writer.
152    ///
153    /// # Errors
154    ///
155    /// Returns the I/O error reported by the wrapped writer.
156    #[inline]
157    fn flush(&mut self) -> Result<()> {
158        self.inner.flush()
159    }
160}
161
162impl<W> Seek for Leb128Writer<W>
163where
164    W: Seek,
165{
166    /// Seeks the wrapped writer.
167    ///
168    /// # Parameters
169    ///
170    /// - `position`: Target seek position.
171    ///
172    /// # Returns
173    ///
174    /// Returns the new stream position.
175    ///
176    /// # Errors
177    ///
178    /// Returns the seek error reported by the wrapped writer.
179    #[inline]
180    fn seek(&mut self, position: SeekFrom) -> Result<u64> {
181        self.inner.seek(position)
182    }
183}