use crate::codec::Mapping;
use crate::{Error, Result};
#[derive(Debug, Clone)]
pub struct StreamingEncoder {
mapping: Mapping,
pending: Vec<u8>,
}
impl StreamingEncoder {
pub fn new() -> Self {
Self::with_mapping(Mapping::identity())
}
pub fn with_mapping(mapping: Mapping) -> Self {
Self {
mapping,
pending: Vec::new(),
}
}
pub fn push(&mut self, chunk: &[u8]) -> String {
self.pending.extend_from_slice(chunk);
let mut out = String::new();
for &b in &self.pending {
out.push(self.mapping.encode_byte(b));
}
self.pending.clear();
out
}
pub fn push_str(&mut self, chunk: &str) -> String {
self.push(chunk.as_bytes())
}
pub fn finish(self) -> Result<String> {
if self.pending.is_empty() {
Ok(String::new())
} else {
let mut out = String::new();
for &b in &self.pending {
out.push(self.mapping.encode_byte(b));
}
Ok(out)
}
}
}
impl Default for StreamingEncoder {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct StreamingDecoder {
mapping: Mapping,
byte_buf: Vec<u8>,
}
impl StreamingDecoder {
pub fn new() -> Self {
Self::with_mapping(Mapping::identity())
}
pub fn with_mapping(mapping: Mapping) -> Self {
Self {
mapping,
byte_buf: Vec::new(),
}
}
pub fn push(&mut self, encoded_chunk: &str) -> Result<String> {
for ch in encoded_chunk.chars() {
let b = self.mapping.decode_char(ch)?;
self.byte_buf.push(b);
}
flush_utf8_prefix(&mut self.byte_buf)
}
pub fn finish(self) -> Result<String> {
if self.byte_buf.is_empty() {
return Ok(String::new());
}
String::from_utf8(self.byte_buf).map_err(|e| Error::InvalidUtf8 {
offset: e.utf8_error().valid_up_to(),
})
}
}
impl Default for StreamingDecoder {
fn default() -> Self {
Self::new()
}
}
fn flush_utf8_prefix(buf: &mut Vec<u8>) -> Result<String> {
match std::str::from_utf8(buf) {
Ok(s) => {
let out = s.to_owned();
buf.clear();
Ok(out)
}
Err(e) => {
let valid_up_to = e.valid_up_to();
if valid_up_to == 0 {
if let Some(error_len) = e.error_len() {
return Err(Error::InvalidUtf8 {
offset: error_len, });
}
return Ok(String::new());
}
let out = std::str::from_utf8(&buf[..valid_up_to])
.map_err(|err| Error::InvalidUtf8 {
offset: err.valid_up_to(),
})?
.to_owned();
let rest = buf[valid_up_to..].to_vec();
*buf = rest;
Ok(out)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stream_roundtrip() {
let text = "Hello from Doldskrift 🚀";
let mut enc = StreamingEncoder::new();
let mut encoded = String::new();
for chunk in text.as_bytes().chunks(3) {
encoded.push_str(&enc.push(chunk));
}
encoded.push_str(&enc.finish().unwrap());
let mut dec = StreamingDecoder::new();
let mut decoded = String::new();
for ch in encoded.chars() {
let mut s = String::new();
s.push(ch);
decoded.push_str(&dec.push(&s).unwrap());
}
decoded.push_str(&dec.finish().unwrap());
assert_eq!(decoded, text);
}
}