Skip to main content

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}