use std::fmt::{
self,
Write,
};
use crate::{
LogOutputLimit,
text::{
log_escape::encode_log_safe_character,
log_output_limit::TRUNCATION_MARKER,
},
};
pub(crate) struct BoundedLogEscapeWriter {
output: String,
max_bytes: usize,
marker_boundary: usize,
truncated: bool,
}
impl BoundedLogEscapeWriter {
#[inline]
pub(crate) fn new(limit: LogOutputLimit) -> Self {
Self {
output: String::new(),
max_bytes: limit.max_bytes(),
marker_boundary: 0,
truncated: false,
}
}
#[inline(always)]
pub(crate) fn finish(self) -> String {
self.output
}
#[inline(always)]
pub(crate) const fn is_truncated(&self) -> bool {
self.truncated
}
fn write_piece(&mut self, piece: &str) -> bool {
if self.truncated {
return false;
}
if piece.len() <= self.max_bytes - self.output.len() {
self.output.push_str(piece);
let payload_limit = self.max_bytes - TRUNCATION_MARKER.len();
if self.output.len() <= payload_limit {
self.marker_boundary = self.output.len();
}
return true;
}
self.output.truncate(self.marker_boundary);
self.output.push_str(TRUNCATION_MARKER);
self.truncated = true;
false
}
}
fn split_debug_escape(value: &str) -> Option<(&str, &str)> {
let bytes = value.as_bytes();
if bytes.first() != Some(&b'\\') {
return None;
}
match bytes.get(1).copied()? {
b'\\' | b'"' | b'n' | b'r' | b't' | b'0' => {
Some((&value[..2], &value[2..]))
}
b'x' if bytes.len() >= 4
&& bytes[2].is_ascii_hexdigit()
&& bytes[3].is_ascii_hexdigit() =>
{
Some((&value[..4], &value[4..]))
}
b'u' if bytes.get(2) == Some(&b'{') => {
let closing = bytes[3..]
.iter()
.position(|byte| *byte == b'}')
.map(|index| index + 3)?;
if closing == 3
|| !bytes[3..closing].iter().all(u8::is_ascii_hexdigit)
{
return None;
}
Some((&value[..=closing], &value[closing + 1..]))
}
_ => None,
}
}
impl Write for BoundedLogEscapeWriter {
fn write_str(&mut self, value: &str) -> fmt::Result {
let mut remaining = value;
while let Some(character) = remaining.chars().next() {
if let Some((escape, rest)) = split_debug_escape(remaining) {
if !self.write_piece(escape) {
return Err(fmt::Error);
}
remaining = rest;
continue;
}
let mut buffer = [0_u8; 12];
let piece = encode_log_safe_character(character, &mut buffer)?;
if !self.write_piece(piece) {
return Err(fmt::Error);
}
remaining = &remaining[character.len_utf8()..];
}
Ok(())
}
}