utf8_builder/lib.rs
1/*!
2# UTF-8 Builder
3
4Build and validate UTF-8 data from chunks. Each chunk doesn't have to be a complete UTF-8 data.
5
6## Motives and Examples
7
8When we want our Rust program to input a UTF-8 data, we can store all data in the memory and use `String::from_utf8(vec)` to validate it and convert it into a `String` instance.
9
10However, it would be better if we perform UTF-8 validation while fetching and storing the data into the memory. In such a way, if the data is not UTF-8, we don't have to waste the memory space and time to store all of it.
11
12```rust
13use utf8_builder::Utf8Builder;
14
15const TEXT1: &str = "is is English.";
16const TEXT2: &str = "這是中文。";
17
18let mut builder = Utf8Builder::new();
19
20builder.push(b'T').unwrap();
21builder.push_char('h').unwrap();
22builder.push_str(TEXT1).unwrap();
23builder.push_chunk(TEXT2.as_bytes()).unwrap();
24
25let result = builder.finalize().unwrap();
26
27assert_eq!(format!("Th{TEXT1}{TEXT2}"), result);
28```
29*/
30
31#![no_std]
32
33extern crate alloc;
34
35mod error;
36
37use alloc::{string::String, vec::Vec};
38use core::{fmt, str::from_utf8};
39
40pub use error::Utf8Error;
41
42/// A builder for building and validating UTF-8 data from chunks.
43#[derive(Debug, Clone, Default)]
44pub struct Utf8Builder {
45 buffer: Vec<u8>,
46 /// the length for the incomplete character
47 sl: u8,
48 /// the valid expected length for the incomplete character
49 sel: u8,
50}
51
52impl Utf8Builder {
53 /// Constructs a new, empty `Utf8Builder`.
54 #[inline]
55 pub const fn new() -> Self {
56 Utf8Builder {
57 buffer: Vec::new(), sl: 0, sel: 0
58 }
59 }
60
61 /// Constructs a new, empty `Utf8Builder` with a specific capacity.
62 #[inline]
63 pub fn with_capacity(capacity: usize) -> Self {
64 Utf8Builder {
65 buffer: Vec::with_capacity(capacity), sl: 0, sel: 0
66 }
67 }
68
69 /// Reserves capacity for at least `additional` more elements to be inserted in the given `Utf8Builder`.
70 #[inline]
71 pub fn reserve(&mut self, additional: usize) {
72 self.buffer.reserve(additional);
73 }
74
75 /// Clears the builder, removing all data but keeping the allocated capacity.
76 #[inline]
77 pub fn clear(&mut self) {
78 self.buffer.clear();
79 self.sl = 0;
80 }
81
82 /// Returns the number of bytes in the buffer, including the bytes of the pending incomplete character.
83 #[inline]
84 pub fn len(&self) -> usize {
85 self.buffer.len()
86 }
87
88 /// Returns `true` if the builder contains no data.
89 #[inline]
90 pub fn is_empty(&self) -> bool {
91 self.buffer.is_empty()
92 }
93
94 /// Returns the current data as a byte slice, including the bytes of the pending incomplete character.
95 #[inline]
96 pub fn as_bytes(&self) -> &[u8] {
97 &self.buffer
98 }
99}
100
101impl Utf8Builder {
102 /// Returns `true` if there is no pending incomplete character.
103 ///
104 /// Invalid bytes are never stored, so the data in the buffer is always valid UTF-8 when this returns `true`.
105 #[inline]
106 pub const fn is_valid(&self) -> bool {
107 self.sl == 0
108 }
109
110 /// Consumes the builder and returns the built `String`.
111 ///
112 /// Returns an error if the data ends with an incomplete character.
113 /// The data is dropped in that case, so use `into_bytes` instead if the raw data is still needed.
114 #[inline]
115 pub fn finalize(self) -> Result<String, Utf8Error> {
116 if self.is_valid() {
117 let s = unsafe { String::from_utf8_unchecked(self.buffer) };
118
119 Ok(s)
120 } else {
121 Err(Utf8Error)
122 }
123 }
124
125 /// Consumes the builder and returns the raw data, even if it ends with an incomplete character.
126 #[inline]
127 pub fn into_bytes(self) -> Vec<u8> {
128 self.buffer
129 }
130}
131
132/// Checks whether `b` can be the `index`-th (1-based) continuation byte of a character starting with `lead`, according to RFC 3629.
133#[inline]
134const fn is_valid_continuation(lead: u8, index: u8, b: u8) -> bool {
135 if index == 1 {
136 match lead {
137 0xE0 => matches!(b, 0xA0..=0xBF),
138 0xED => matches!(b, 0x80..=0x9F),
139 0xF0 => matches!(b, 0x90..=0xBF),
140 0xF4 => matches!(b, 0x80..=0x8F),
141 _ => matches!(b, 0x80..=0xBF),
142 }
143 } else {
144 matches!(b, 0x80..=0xBF)
145 }
146}
147
148impl Utf8Builder {
149 /// Pushes a byte.
150 ///
151 /// Returns an error if the byte is not allowed at the current position.
152 /// Nothing is stored in that case, so a correct byte can still be pushed afterwards.
153 #[inline]
154 pub fn push(&mut self, b: u8) -> Result<(), Utf8Error> {
155 if self.sl == 0 {
156 let w = utf8_width::get_width(b);
157
158 match w {
159 0 => return Err(Utf8Error),
160 1 => {
161 self.buffer.push(b);
162 },
163 _ => {
164 self.buffer.push(b);
165 self.sl = 1;
166 self.sel = w as u8;
167 },
168 }
169 } else {
170 // the lead byte of the pending incomplete character is still in the buffer
171 let lead = self.buffer[self.buffer.len() - self.sl as usize];
172
173 if !is_valid_continuation(lead, self.sl, b) {
174 return Err(Utf8Error);
175 }
176
177 self.buffer.push(b);
178
179 if self.sl + 1 == self.sel {
180 self.sl = 0;
181 // self.sel = 0; // no need
182 } else {
183 self.sl += 1;
184 }
185 }
186
187 Ok(())
188 }
189
190 /// Pushes a `&str`.
191 ///
192 /// Returns an error if there is a pending incomplete character.
193 /// The builder state is unchanged in that case.
194 #[inline]
195 pub fn push_str(&mut self, s: &str) -> Result<(), Utf8Error> {
196 if self.sl == 0 {
197 self.buffer.extend_from_slice(s.as_bytes());
198
199 Ok(())
200 } else {
201 Err(Utf8Error)
202 }
203 }
204
205 /// Pushes a char.
206 ///
207 /// Returns an error if there is a pending incomplete character.
208 /// The builder state is unchanged in that case.
209 #[inline]
210 pub fn push_char(&mut self, c: char) -> Result<(), Utf8Error> {
211 if self.sl == 0 {
212 let mut tmp = [0u8; 4];
213
214 self.buffer.extend_from_slice(c.encode_utf8(&mut tmp).as_bytes());
215
216 Ok(())
217 } else {
218 Err(Utf8Error)
219 }
220 }
221
222 /// Pushes a chunk.
223 ///
224 /// The chunk does not have to be complete UTF-8 data, because a character can be split across chunks.
225 /// Returns an error if the chunk contains invalid data.
226 /// In that case, the leading valid part of the chunk is still stored, so the builder remains usable.
227 /// However, if the error happens while completing a pending incomplete character, nothing is stored.
228 #[inline]
229 pub fn push_chunk(&mut self, chunk: &[u8]) -> Result<(), Utf8Error> {
230 let chunk_size = chunk.len();
231
232 if chunk_size == 0 {
233 return Ok(());
234 }
235
236 let e = if self.sl > 0 {
237 // the lead byte of the pending incomplete character is still in the buffer
238 let lead = self.buffer[self.buffer.len() - self.sl as usize];
239
240 let r = (self.sel - self.sl) as usize;
241 let take = r.min(chunk_size);
242
243 for (i, &b) in chunk[..take].iter().enumerate() {
244 if !is_valid_continuation(lead, self.sl + i as u8, b) {
245 return Err(Utf8Error);
246 }
247 }
248
249 self.buffer.extend_from_slice(&chunk[..take]);
250
251 if take < r {
252 self.sl += take as u8;
253
254 return Ok(());
255 }
256
257 self.sl = 0;
258 // self.sel = 0; // no need
259
260 if take == chunk_size {
261 return Ok(());
262 }
263
264 take
265 } else {
266 0usize
267 };
268
269 let rest = &chunk[e..];
270
271 match from_utf8(rest) {
272 Ok(s) => {
273 self.buffer.extend_from_slice(s.as_bytes());
274 },
275 Err(error) => {
276 let valid_up_to = error.valid_up_to();
277
278 if error.error_len().is_some() {
279 // still keep the leading valid characters so the builder remains usable
280 self.buffer.extend_from_slice(&rest[..valid_up_to]);
281
282 return Err(Utf8Error);
283 }
284
285 // the chunk ends with an incomplete character which is guaranteed to be a valid prefix
286 self.buffer.extend_from_slice(rest);
287
288 self.sl = (rest.len() - valid_up_to) as u8;
289 self.sel = utf8_width::get_width(rest[valid_up_to]) as u8;
290 },
291 }
292
293 Ok(())
294 }
295}
296
297/// Writing fails if there is a pending incomplete character.
298impl fmt::Write for Utf8Builder {
299 #[inline]
300 fn write_str(&mut self, s: &str) -> fmt::Result {
301 self.push_str(s).map_err(|_| fmt::Error)
302 }
303
304 #[inline]
305 fn write_char(&mut self, c: char) -> fmt::Result {
306 self.push_char(c).map_err(|_| fmt::Error)
307 }
308}
309
310impl From<&str> for Utf8Builder {
311 #[inline]
312 fn from(s: &str) -> Self {
313 Utf8Builder {
314 buffer: s.as_bytes().to_vec(), sl: 0, sel: 0
315 }
316 }
317}
318
319impl From<String> for Utf8Builder {
320 #[inline]
321 fn from(s: String) -> Self {
322 Utf8Builder {
323 buffer: s.into_bytes(), sl: 0, sel: 0
324 }
325 }
326}