Skip to main content

tuwunel_core/utils/
debug.rs

1use std::fmt;
2
3/// Debug-formats the given slice, but only up to the first `max_len` elements.
4/// Any further elements are replaced by an ellipsis.
5///
6/// See also [`slice_truncated()`],
7pub struct TruncatedSlice<'a, T> {
8	inner: &'a [T],
9	max_len: usize,
10}
11
12/// Debug-formats the given str, but only up to the first `max_len` elements.
13/// Any further elements are replaced by an ellipsis.
14///
15/// See also [`str_truncated()`],
16pub struct TruncatedStr<'a> {
17	inner: &'a str,
18	max_len: usize,
19}
20
21/// See [`TruncatedSlice`]. Useful for `#[instrument]`:
22///
23/// ```
24/// use tuwunel_core::utils::debug::slice_truncated;
25///
26/// #[tracing::instrument(fields(foos = slice_truncated(foos, 42)))]
27/// fn bar(foos: &[&str]) {}
28/// ```
29pub fn slice_truncated<T: fmt::Debug>(
30	slice: &[T],
31	max_len: usize,
32) -> tracing::field::DebugValue<TruncatedSlice<'_, T>> {
33	tracing::field::debug(TruncatedSlice { inner: slice, max_len })
34}
35
36/// See [`TruncatedStr`]. Useful for `#[instrument]`:
37///
38/// ```
39/// use tuwunel_core::utils::debug::str_truncated;
40///
41/// #[tracing::instrument(fields(foos = str_truncated(foos, 42)))]
42/// fn bar(foos: &str) {}
43/// ```
44#[must_use]
45pub fn str_truncated(s: &str, max_len: usize) -> tracing::field::DebugValue<TruncatedStr<'_>> {
46	tracing::field::debug(TruncatedStr { inner: s, max_len })
47}
48
49#[macro_export]
50macro_rules! redacted_debug {
51	($f:ident) => {
52		if $f.is_some() { "Some(<redacted>)" } else { "None" }
53	};
54}
55
56impl<T: fmt::Debug> fmt::Debug for TruncatedSlice<'_, T> {
57	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58		if self.inner.len() <= self.max_len {
59			write!(f, "{:?}", self.inner)
60		} else {
61			f.debug_list()
62				.entries(&self.inner[..self.max_len])
63				.entry(&"...")
64				.finish()
65		}
66	}
67}
68
69impl fmt::Debug for TruncatedStr<'_> {
70	#[expect(clippy::string_slice)]
71	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72		if self.inner.len() <= self.max_len {
73			write!(f, "{:?}", self.inner)
74		} else {
75			let len = self
76				.inner
77				.char_indices()
78				.skip_while(|(i, _)| *i < self.max_len)
79				.map(|(i, _)| i)
80				.next()
81				.expect("At least one char_indice >= len for str");
82
83			write!(f, "{:?}...", &self.inner[..len])
84		}
85	}
86}