#[derive(Debug, Default)]
pub struct Utf8StreamDecoder {
pending: Vec<u8>,
}
impl Utf8StreamDecoder {
pub fn decode_into(&mut self, chunk: &[u8], out: &mut String) {
let owned;
let mut rest: &[u8] = if self.pending.is_empty() {
chunk
} else {
self.pending.extend_from_slice(chunk);
owned = std::mem::take(&mut self.pending);
&owned
};
loop {
match std::str::from_utf8(rest) {
Ok(s) => {
out.push_str(s);
return;
}
Err(e) => {
let (valid, after) = rest.split_at(e.valid_up_to());
out.push_str(std::str::from_utf8(valid).expect("valid prefix"));
match e.error_len() {
Some(n) => {
out.push(char::REPLACEMENT_CHARACTER);
rest = &after[n..];
}
None => {
self.pending = after.to_vec();
return;
}
}
}
}
}
}
pub fn flush(&mut self, out: &mut String) {
if !self.pending.is_empty() {
self.pending.clear();
out.push(char::REPLACEMENT_CHARACTER);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn multibyte_char_split_across_chunks() {
let bytes = "汉字🚀".as_bytes(); let mut d = Utf8StreamDecoder::default();
let mut out = String::new();
for b in bytes {
d.decode_into(std::slice::from_ref(b), &mut out);
}
d.flush(&mut out);
assert_eq!(out, "汉字🚀");
}
#[test]
fn invalid_bytes_replaced_incomplete_tail_flushed() {
let mut d = Utf8StreamDecoder::default();
let mut out = String::new();
d.decode_into(b"a\xffb\xe6\xb1", &mut out); assert_eq!(out, "a\u{fffd}b");
d.flush(&mut out);
assert_eq!(out, "a\u{fffd}b\u{fffd}");
}
}