use std::fmt::{
self,
Debug,
Display,
Formatter,
Write as _,
};
use crate::{
LogOutputLimit,
text::internal::BoundedLogEscapeWriter,
};
use super::internal::with_mask_byte_limit;
#[must_use = "format the bounded redacted view"]
pub struct BoundedRedactedDisplay<D> {
value: D,
limit: LogOutputLimit,
}
impl<D> BoundedRedactedDisplay<D> {
#[inline(always)]
pub(crate) const fn new(value: D, limit: LogOutputLimit) -> Self {
Self { value, limit }
}
}
impl<D: Debug> Display for BoundedRedactedDisplay<D> {
#[inline(always)]
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
format_bounded(&self.value, self.limit, formatter)
}
}
impl<D: Debug> Debug for BoundedRedactedDisplay<D> {
#[inline(always)]
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
format_bounded(&self.value, self.limit, formatter)
}
}
pub(super) fn format_bounded(
value: &dyn Debug,
limit: LogOutputLimit,
formatter: &mut Formatter<'_>,
) -> fmt::Result {
let mut writer = BoundedLogEscapeWriter::new(limit);
let result = with_mask_byte_limit(limit.max_bytes(), || {
write!(&mut writer, "{value:?}")
});
if result.is_err() && !writer.is_truncated() {
return Err(fmt::Error);
}
formatter.write_str(&writer.finish())
}
pub(super) fn format_debug_bounded(
value: &dyn Debug,
limit: LogOutputLimit,
formatter: &mut Formatter<'_>,
) -> fmt::Result {
let mut writer = BoundedDebugWriter::new(limit);
let result = with_mask_byte_limit(limit.max_bytes(), || {
if formatter.alternate() {
write!(&mut writer, "{value:#?}")
} else {
write!(&mut writer, "{value:?}")
}
});
if result.is_err() && !writer.is_truncated() {
return Err(fmt::Error);
}
formatter.write_str(&writer.finish())
}
struct BoundedDebugWriter {
output: String,
limit: usize,
truncated: bool,
}
impl BoundedDebugWriter {
fn new(limit: LogOutputLimit) -> Self {
Self {
output: String::new(),
limit: limit.max_bytes(),
truncated: false,
}
}
fn is_truncated(&self) -> bool {
self.truncated
}
fn finish(mut self) -> String {
if self.truncated {
let marker = "<truncated>";
let prefix_limit = self.limit.saturating_sub(marker.len());
self.output.truncate(prefix_limit.min(self.output.len()));
self.output.push_str(marker);
}
self.output
}
}
impl fmt::Write for BoundedDebugWriter {
fn write_str(&mut self, value: &str) -> fmt::Result {
if self.truncated {
return Err(fmt::Error);
}
let next_len = self.output.len().saturating_add(value.len());
if next_len <= self.limit {
self.output.push_str(value);
return Ok(());
}
let payload_limit = self.limit.saturating_sub("<truncated>".len());
let remaining = payload_limit.saturating_sub(self.output.len());
if remaining > 0 {
let prefix = value
.get(..remaining)
.or_else(|| value.get(..floor_char_boundary(value, remaining)))
.unwrap_or_default();
self.output.push_str(prefix);
}
self.truncated = true;
Err(fmt::Error)
}
}
fn floor_char_boundary(value: &str, limit: usize) -> usize {
let mut boundary = limit.min(value.len());
while boundary > 0 && !value.is_char_boundary(boundary) {
boundary -= 1;
}
boundary
}