Skip to main content

qubit_io/codecs/
zig_zag_reader.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 ******************************************************************************/
10use std::io::{
11    BufRead,
12    Read,
13    Result,
14    Seek,
15    SeekFrom,
16};
17
18use crate::ZigZagReadExt;
19
20/// Reader wrapper for ZigZag encoded signed integers.
21///
22/// This wrapper defaults to non-strict decoding of the underlying unsigned
23/// LEB128 value. Use [`ZigZagReader::with_strict`] or
24/// [`ZigZagReader::set_strict`] to reject non-canonical underlying encodings.
25///
26/// # Examples
27/// ```
28/// use std::io::Cursor;
29///
30/// use qubit_io::{
31///     ZigZagReader,
32///     ZigZagWriter,
33/// };
34///
35/// let mut output = ZigZagWriter::new(Vec::new());
36/// output.write_i32(-123)?;
37///
38/// let mut input = ZigZagReader::with_strict(Cursor::new(output.into_inner()), true);
39/// assert_eq!(-123, input.read_i32()?);
40/// # Ok::<(), std::io::Error>(())
41/// ```
42pub struct ZigZagReader<R> {
43    inner: R,
44    strict: bool,
45}
46
47impl<R> ZigZagReader<R> {
48    /// Creates a ZigZag reader.
49    ///
50    /// # Parameters
51    /// - `inner`: Reader to wrap.
52    ///
53    /// # Returns
54    /// A new non-strict ZigZag reader.
55    #[inline]
56    pub fn new(inner: R) -> Self {
57        Self::with_strict(inner, false)
58    }
59
60    /// Creates a ZigZag reader with explicit underlying LEB128 policy.
61    ///
62    /// # Parameters
63    /// - `inner`: Reader to wrap.
64    /// - `strict`: Whether to reject non-canonical underlying LEB128 encodings.
65    ///
66    /// # Returns
67    /// A new ZigZag reader.
68    #[inline]
69    pub fn with_strict(inner: R, strict: bool) -> Self {
70        Self { inner, strict }
71    }
72
73    /// Reports whether strict underlying LEB128 decoding is enabled.
74    ///
75    /// # Returns
76    /// `true` when non-canonical underlying LEB128 encodings are rejected.
77    #[inline]
78    pub fn is_strict(&self) -> bool {
79        self.strict
80    }
81
82    /// Changes the underlying LEB128 policy used by subsequent reads.
83    ///
84    /// # Parameters
85    /// - `strict`: Whether to reject non-canonical underlying LEB128 encodings.
86    #[inline]
87    pub fn set_strict(&mut self, strict: bool) {
88        self.strict = strict;
89    }
90
91    /// Returns an immutable reference to the wrapped reader.
92    ///
93    /// # Returns
94    /// The wrapped reader reference.
95    #[inline]
96    pub fn get_ref(&self) -> &R {
97        &self.inner
98    }
99
100    /// Returns a mutable reference to the wrapped reader.
101    ///
102    /// # Returns
103    /// The wrapped reader reference.
104    #[inline]
105    pub fn get_mut(&mut self) -> &mut R {
106        &mut self.inner
107    }
108
109    /// Consumes this wrapper and returns the wrapped reader.
110    ///
111    /// # Returns
112    /// The wrapped reader.
113    #[inline]
114    pub fn into_inner(self) -> R {
115        self.inner
116    }
117}
118
119macro_rules! delegate_read {
120    ($name:ident, $read:ident, $read_strict:ident, $value:ty) => {
121        #[doc = concat!("Reads a ZigZag encoded `", stringify!($value), "`.")]
122        ///
123        /// # Errors
124        /// Returns an I/O error from the wrapped reader, or `InvalidData` for
125        /// malformed or overflowing underlying unsigned LEB128 input. In strict
126        /// mode, also returns `InvalidData` for non-canonical underlying LEB128
127        /// input.
128        #[inline]
129        pub fn $name(&mut self) -> Result<$value> {
130            if self.strict {
131                self.inner.$read_strict()
132            } else {
133                self.inner.$read()
134            }
135        }
136    };
137}
138
139impl<R> ZigZagReader<R>
140where
141    R: Read,
142{
143    delegate_read!(read_i8, read_zigzag_i8, read_zigzag_i8_strict, i8);
144    delegate_read!(read_i16, read_zigzag_i16, read_zigzag_i16_strict, i16);
145    delegate_read!(read_i32, read_zigzag_i32, read_zigzag_i32_strict, i32);
146    delegate_read!(read_i64, read_zigzag_i64, read_zigzag_i64_strict, i64);
147    delegate_read!(read_i128, read_zigzag_i128, read_zigzag_i128_strict, i128);
148    delegate_read!(
149        read_isize,
150        read_zigzag_isize,
151        read_zigzag_isize_strict,
152        isize
153    );
154}
155
156impl<R> Read for ZigZagReader<R>
157where
158    R: Read,
159{
160    #[inline]
161    fn read(&mut self, buffer: &mut [u8]) -> Result<usize> {
162        self.inner.read(buffer)
163    }
164}
165
166impl<R> BufRead for ZigZagReader<R>
167where
168    R: BufRead,
169{
170    #[inline]
171    fn fill_buf(&mut self) -> Result<&[u8]> {
172        self.inner.fill_buf()
173    }
174
175    #[inline]
176    fn consume(&mut self, amount: usize) {
177        self.inner.consume(amount);
178    }
179}
180
181impl<R> Seek for ZigZagReader<R>
182where
183    R: Seek,
184{
185    #[inline]
186    fn seek(&mut self, position: SeekFrom) -> Result<u64> {
187        self.inner.seek(position)
188    }
189}