qubit_io/ext/buf_read_ext.rs
1// =============================================================================
2// Copyright (c) 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8use std::io::{
9 BufRead,
10 Error,
11 ErrorKind,
12 Result,
13};
14
15use crate::ext::internal::read_ext_impl;
16use crate::util::{
17 try_reserve_string,
18 try_reserve_vec,
19};
20
21/// Extension methods for [`BufRead`] values.
22///
23/// `BufReadExt` provides bounded delimiter-oriented reads. These helpers are
24/// useful for line-based and delimiter-based formats where accepting unbounded
25/// input would make parsers vulnerable to excessive memory use.
26pub trait BufReadExt: BufRead {
27 /// Reads bytes through `delimiter` while enforcing `max_len`.
28 ///
29 /// The returned vector includes the delimiter when it is found. EOF before
30 /// the delimiter is accepted as long as the accumulated bytes do not exceed
31 /// `max_len`. If the limit is exceeded, no vector is returned and the
32 /// reader may still consume bytes while detecting the violation.
33 ///
34 /// # Parameters
35 /// - `delimiter`: Delimiter byte to search for.
36 /// - `max_len`: Maximum accepted result length, including the delimiter.
37 ///
38 /// # Returns
39 /// Bytes read from the stream.
40 ///
41 /// # Errors
42 /// Returns [`ErrorKind::InvalidData`] when more than `max_len` bytes are
43 /// required before reaching `delimiter` or EOF. Returns the first I/O error
44 /// reported by the underlying reader.
45 fn read_until_limited(
46 &mut self,
47 delimiter: u8,
48 max_len: usize,
49 ) -> Result<Vec<u8>>;
50
51 /// Reads bytes through `delimiter` into `output` while enforcing `max_len`.
52 ///
53 /// This method appends at most `max_len` bytes from the current reader
54 /// position to `output`. The delimiter is included when it is found. If the
55 /// limit is exceeded, `output` is truncated back to its original length.
56 /// The reader may still consume bytes while detecting the violation.
57 ///
58 /// # Parameters
59 /// - `delimiter`: Delimiter byte to search for.
60 /// - `output`: Destination vector to append to.
61 /// - `max_len`: Maximum accepted result length, including the delimiter.
62 ///
63 /// # Returns
64 /// Number of bytes appended to `output`.
65 ///
66 /// # Errors
67 /// Returns [`ErrorKind::InvalidData`] when more than `max_len` bytes are
68 /// required before reaching `delimiter` or EOF. Returns the first I/O error
69 /// reported by the underlying reader.
70 fn read_until_limited_into(
71 &mut self,
72 delimiter: u8,
73 output: &mut Vec<u8>,
74 max_len: usize,
75 ) -> Result<usize>;
76
77 /// Reads one UTF-8 line while enforcing `max_len`.
78 ///
79 /// The returned string includes the trailing `\n` when it is present. EOF
80 /// before a newline is accepted as long as the accumulated bytes do not
81 /// exceed `max_len`.
82 ///
83 /// # Parameters
84 /// - `max_len`: Maximum accepted line length in bytes, including `\n`.
85 ///
86 /// # Returns
87 /// The decoded UTF-8 line.
88 ///
89 /// # Errors
90 /// Returns [`ErrorKind::InvalidData`] when the line exceeds `max_len` or is
91 /// not valid UTF-8. Returns the first I/O error reported by the underlying
92 /// reader.
93 fn read_line_limited(&mut self, max_len: usize) -> Result<String>;
94
95 /// Reads one UTF-8 line into `output` while enforcing `max_len`.
96 ///
97 /// This method reads at most `max_len` bytes, validates the line as UTF-8,
98 /// and appends it to `output`. If the line is oversized or invalid UTF-8,
99 /// `output` is truncated back to its original length. Oversized input may
100 /// still consume bytes from the reader while detecting the limit violation.
101 ///
102 /// # Parameters
103 /// - `output`: Destination string to append to.
104 /// - `max_len`: Maximum accepted line length in bytes, including `\n`.
105 ///
106 /// # Returns
107 /// Number of bytes appended to `output`.
108 ///
109 /// # Errors
110 /// Returns [`ErrorKind::InvalidData`] when the line exceeds `max_len` or is
111 /// not valid UTF-8. Returns the first I/O error reported by the underlying
112 /// reader.
113 fn read_line_limited_into(
114 &mut self,
115 output: &mut String,
116 max_len: usize,
117 ) -> Result<usize>;
118
119 /// Discards bytes through `delimiter` while enforcing `max_len`.
120 ///
121 /// The delimiter is consumed when it is found. EOF before the delimiter is
122 /// accepted as long as no more than `max_len` bytes are consumed.
123 ///
124 /// # Parameters
125 /// - `delimiter`: Delimiter byte to search for.
126 /// - `max_len`: Maximum number of bytes to discard, including the
127 /// delimiter.
128 ///
129 /// # Returns
130 /// Number of bytes discarded.
131 ///
132 /// # Errors
133 /// Returns [`ErrorKind::InvalidData`] when more than `max_len` bytes are
134 /// required before reaching `delimiter` or EOF. Returns the first I/O error
135 /// reported by the underlying reader.
136 fn discard_until_limited(
137 &mut self,
138 delimiter: u8,
139 max_len: usize,
140 ) -> Result<usize>;
141}
142
143impl<T> BufReadExt for T
144where
145 T: BufRead + ?Sized,
146{
147 #[inline]
148 fn read_until_limited(
149 &mut self,
150 delimiter: u8,
151 max_len: usize,
152 ) -> Result<Vec<u8>> {
153 let mut output = Vec::new();
154 try_reserve_vec(&mut output, max_len.min(8192))?;
155 read_ext_impl::read_until_limited_into(
156 self,
157 delimiter,
158 &mut output,
159 max_len,
160 )?;
161 Ok(output)
162 }
163
164 #[inline]
165 fn read_until_limited_into(
166 &mut self,
167 delimiter: u8,
168 output: &mut Vec<u8>,
169 max_len: usize,
170 ) -> Result<usize> {
171 read_ext_impl::read_until_limited_into(self, delimiter, output, max_len)
172 }
173
174 #[inline]
175 fn read_line_limited(&mut self, max_len: usize) -> Result<String> {
176 let mut output = String::new();
177 self.read_line_limited_into(&mut output, max_len)?;
178 Ok(output)
179 }
180
181 fn read_line_limited_into(
182 &mut self,
183 output: &mut String,
184 max_len: usize,
185 ) -> Result<usize> {
186 let original_len = output.len();
187 let mut bytes = Vec::new();
188 try_reserve_vec(&mut bytes, max_len.min(8192))?;
189 let result = (|| {
190 let count = read_ext_impl::read_until_limited_into(
191 self, b'\n', &mut bytes, max_len,
192 )?;
193 let line = String::from_utf8(bytes).map_err(|error| {
194 Error::new(
195 ErrorKind::InvalidData,
196 format!("limited line is not valid UTF-8: {error}"),
197 )
198 })?;
199 try_reserve_string(output, line.len())?;
200 output.push_str(&line);
201 Ok(count)
202 })();
203 if result.is_err() {
204 output.truncate(original_len);
205 }
206 result
207 }
208
209 fn discard_until_limited(
210 &mut self,
211 delimiter: u8,
212 max_len: usize,
213 ) -> Result<usize> {
214 let mut discarded = 0;
215 loop {
216 let available = self.fill_buf()?;
217 if available.is_empty() {
218 return Ok(discarded);
219 }
220
221 let delimiter_position =
222 available.iter().position(|byte| *byte == delimiter);
223 let requested = delimiter_position
224 .map_or(available.len(), |position| position + 1);
225 let remaining = max_len.saturating_sub(discarded);
226 if requested > remaining {
227 if remaining > 0 {
228 self.consume(remaining);
229 }
230 return Err(Error::new(
231 ErrorKind::InvalidData,
232 format!(
233 "input exceeds maximum length of {max_len} bytes before delimiter {delimiter}"
234 ),
235 ));
236 }
237
238 self.consume(requested);
239 discarded += requested;
240 if delimiter_position.is_some() {
241 return Ok(discarded);
242 }
243 }
244 }
245}