vtcode_commons/
utf8_stream.rs1use std::borrow::Cow;
4
5#[derive(Debug, Default)]
16pub struct Utf8StreamDecoder {
17 pending: Vec<u8>,
18}
19
20impl Utf8StreamDecoder {
21 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn push(&mut self, bytes: &[u8]) -> String {
28 let mut out = Vec::with_capacity(bytes.len());
29 self.push_bytes(bytes, &mut out);
30 String::from_utf8(out).unwrap_or_else(|error| String::from_utf8_lossy(error.as_bytes()).into_owned())
32 }
33
34 pub fn push_cow<'a>(&mut self, bytes: &'a [u8]) -> Cow<'a, str> {
38 if self.pending.is_empty()
39 && let Ok(text) = std::str::from_utf8(bytes)
40 {
41 return Cow::Borrowed(text);
42 }
43 Cow::Owned(self.push(bytes))
44 }
45
46 pub fn push_bytes(&mut self, bytes: &[u8], out: &mut Vec<u8>) {
53 self.pending.extend_from_slice(bytes);
54 loop {
55 match std::str::from_utf8(&self.pending) {
56 Ok(text) => {
57 out.extend_from_slice(text.as_bytes());
58 self.pending.clear();
59 break;
60 }
61 Err(err) => {
62 let valid = err.valid_up_to();
63 if let Some(valid_bytes) = self.pending.get(..valid) {
64 out.extend_from_slice(valid_bytes);
66 }
67 match err.error_len() {
68 Some(invalid_len) => {
70 out.extend_from_slice("\u{FFFD}".as_bytes());
71 let _ = self.pending.drain(..valid + invalid_len);
72 }
73 None => {
75 let _ = self.pending.drain(..valid);
76 break;
77 }
78 }
79 }
80 }
81 }
82 }
83
84 pub fn finish(&mut self) -> String {
87 let pending = std::mem::take(&mut self.pending);
88 String::from_utf8_lossy(&pending).into_owned()
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn push_bytes_matches_push_byte_by_byte() {
98 let full = "data: {\"hello\":\"world\"} xin chào 世界\n\n".as_bytes();
99 let mut dec_str = Utf8StreamDecoder::new();
100 let mut dec_bytes = Utf8StreamDecoder::new();
101 let mut joined = String::new();
102 for (i, &byte) in full.iter().enumerate() {
103 let mut out = Vec::new();
104 dec_bytes.push_bytes(std::slice::from_ref(&byte), &mut out);
105 let s = dec_str.push(std::slice::from_ref(&byte));
106 assert_eq!(out, s.clone().into_bytes(), "byte {i}: push_bytes must match push");
107 joined.push_str(&s);
108 }
109 assert_eq!(joined.as_bytes(), full);
110 assert!(dec_str.finish().is_empty());
111 }
112
113 #[test]
114 fn split_multibyte_is_held_until_complete() {
115 let mut dec = Utf8StreamDecoder::new();
117 assert!(dec.push(&[0xC3]).is_empty(), "incomplete multibyte should produce no output");
118 assert_eq!(dec.push(&[0xA9, b'h', b'i']), "\u{00E9}hi");
119 }
120
121 #[test]
122 fn invalid_bytes_become_replacement_characters() {
123 let mut dec = Utf8StreamDecoder::new();
125 assert_eq!(dec.push(&[b'a', 0xFF, b'b']), "a\u{FFFD}b");
126 }
127
128 #[test]
129 fn push_cow_borrows_valid_chunks_and_buffers_splits() {
130 let mut dec = Utf8StreamDecoder::new();
131 assert!(matches!(dec.push_cow("plain 世".as_bytes()), Cow::Borrowed("plain 世")));
132 assert_eq!(dec.push_cow(&[0xE7, 0x95]), "");
134 let completed = dec.push_cow(&[0x8C, b'!']);
135 assert!(matches!(completed, Cow::Owned(_)));
136 assert_eq!(completed, "界!");
137 assert!(dec.finish().is_empty());
138 }
139
140 #[test]
141 fn finish_flushes_a_truncated_final_sequence() {
142 let mut dec = Utf8StreamDecoder::new();
143 assert_eq!(dec.push(&[b'x', 0xE2, 0x80]), "x");
145 assert_eq!(dec.finish(), "\u{FFFD}");
146 assert!(dec.finish().is_empty(), "finish leaves the decoder empty");
147 }
148}