fedimint_core/
fmt_utils.rs1use std::{cmp, fmt, ops, thread_local};
2
3use serde_json::Value;
4
5pub fn rust_log_full_enabled() -> bool {
6 thread_local!(static RUST_LOG_FULL: bool = {
8 std::env::var_os("RUST_LOG_FULL").is_some_and(|val| !val.is_empty())
9 });
10 RUST_LOG_FULL.with(|x| *x)
11}
12
13pub struct OptStacktrace<T>(pub T);
20
21impl<T> fmt::Display for OptStacktrace<T>
22where
23 T: fmt::Debug + fmt::Display,
24{
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 if rust_log_full_enabled() {
27 fmt::Debug::fmt(&self.0, f)
28 } else {
29 fmt::Display::fmt(&self.0, f)
30 }
31 }
32}
33
34pub struct AbbreviateHexBytes<'a>(pub &'a [u8]);
39
40impl<'a> fmt::Display for AbbreviateHexBytes<'a> {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 if self.0.len() <= 64 || rust_log_full_enabled() {
43 fedimint_core::format_hex(self.0, f)?;
44 } else {
45 fedimint_core::format_hex(&self.0[..64], f)?;
46 f.write_fmt(format_args!("-{}", self.0.len()))?;
47 }
48 Ok(())
49 }
50}
51
52impl<'a> fmt::Debug for AbbreviateHexBytes<'a> {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 fmt::Display::fmt(self, f)
55 }
56}
57
58pub struct AbbreviateJson<'a>(pub &'a serde_json::Value);
64
65pub fn floor_char_boundary(s: &str, index: usize) -> usize {
68 #[inline]
70 pub const fn is_utf8_char_boundary(byte: u8) -> bool {
71 (byte as i8) >= -0x40
73 }
74
75 if index >= s.len() {
76 s.len()
77 } else {
78 let lower_bound = index.saturating_sub(3);
79 let new_index = s.as_bytes()[lower_bound..=index]
80 .iter()
81 .rposition(|b| is_utf8_char_boundary(*b));
82
83 unsafe { lower_bound + new_index.unwrap_unchecked() }
85 }
86}
87
88fn fmt_abbreviated_str(value: &str, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
90 const STRING_ABBR_LEN: usize = 128;
91 fmt::Debug::fmt(
92 &value[..floor_char_boundary(value, cmp::min(STRING_ABBR_LEN, value.len()))],
93 formatter,
94 )?;
95 if STRING_ABBR_LEN < value.len() {
96 formatter.write_fmt(format_args!("... {} total", value.len()))?;
97 }
98 Ok(())
99}
100
101fn fmt_abbreviated_vec(vec: &[Value], formatter: &mut fmt::Formatter) -> fmt::Result {
104 const ARRAY_ABBR_LEN: usize = 64;
105 formatter.write_str("[")?;
106 for (i, v) in vec.iter().enumerate().take(ARRAY_ABBR_LEN) {
107 fmt::Debug::fmt(&AbbreviateJson(v), formatter)?;
108 if i != vec.len() - 1 {
109 formatter.write_str(", ")?;
110 }
111 }
112 if ARRAY_ABBR_LEN < vec.len() {
113 formatter.write_fmt(format_args!("... {} total", vec.len()))?;
114 }
115 formatter.write_str("]")?;
116 Ok(())
117}
118
119fn fmt_abbreviated_object(
122 map: &serde_json::Map<String, Value>,
123 formatter: &mut fmt::Formatter,
124) -> fmt::Result {
125 const MAP_ABBR_LEN: usize = 64;
126 formatter.write_str("{")?;
127 for (i, (k, v)) in map.iter().enumerate().take(MAP_ABBR_LEN) {
128 fmt_abbreviated_str(k, formatter)?;
129 formatter.write_str(": ")?;
130 fmt::Debug::fmt(&AbbreviateJson(v), formatter)?;
131 if i != map.len() - 1 {
132 formatter.write_str(", ")?;
133 }
134 }
135 if MAP_ABBR_LEN < map.len() {
136 formatter.write_fmt(format_args!("... {} total", map.len()))?;
137 }
138 formatter.write_str("}")?;
139 Ok(())
140}
141
142impl<'a> fmt::Debug for AbbreviateJson<'a> {
143 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
144 if rust_log_full_enabled() {
145 std::fmt::Debug::fmt(&self.0, formatter)
146 } else {
147 match self.0 {
149 Value::Null => formatter.write_str("Null"),
150 Value::Bool(boolean) => write!(formatter, "Bool({boolean})"),
151 Value::Number(number) => fmt::Debug::fmt(number, formatter),
152 Value::String(string) => {
153 formatter.write_str("String(")?;
154 fmt_abbreviated_str(string, formatter)?;
155 formatter.write_str(")")
156 }
157 Value::Array(vec) => {
158 formatter.write_str("Array ")?;
159 fmt_abbreviated_vec(vec, formatter)
160 }
161 Value::Object(map) => {
162 formatter.write_str("Object ")?;
163 fmt_abbreviated_object(map, formatter)
164 }
165 }
166 }
167 }
168}
169
170pub trait AbbreviatedDebug {
172 fn abbreviated_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
173}
174
175pub struct AbbreviateDebug<T>(pub T);
182
183impl<T> fmt::Debug for AbbreviateDebug<T>
184where
185 T: AbbreviatedDebug,
186{
187 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188 self.0.abbreviated_fmt(f)
189 }
190}
191
192impl<T> ops::Deref for AbbreviateDebug<T> {
193 type Target = T;
194
195 fn deref(&self) -> &Self::Target {
196 &self.0
197 }
198}
199
200impl AbbreviatedDebug for serde_json::Value {
201 fn abbreviated_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 fmt::Debug::fmt(&AbbreviateJson(self), f)
203 }
204}
205
206impl<const N: usize> AbbreviatedDebug for [u8; N] {
207 fn abbreviated_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 fmt::Debug::fmt(&AbbreviateHexBytes(self), f)
209 }
210}
211
212impl AbbreviatedDebug for &[serde_json::Value] {
213 fn abbreviated_fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 fmt_abbreviated_vec(self, f)
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn sanity_check_abbreviate_json() {
224 for v in [
225 serde_json::json!(null),
226 serde_json::json!(true),
227 serde_json::json!(false),
228 serde_json::json!("foo"),
229 serde_json::json!({}),
230 serde_json::json!([]),
231 serde_json::json!([1]),
232 serde_json::json!([1, 3, 4]),
233 serde_json::json!({"a": "b"}),
234 serde_json::json!({"a": "b", "c": "d"}),
235 serde_json::json!({"a": { "foo": "bar"}, "c": "d"}),
236 serde_json::json!({"a": [1, 2, 3, 4], "b": {"c": "d"}}),
237 serde_json::json!([{"a": "b"}]),
238 serde_json::json!([{"a": "b"}, {"d": "f"}]),
239 serde_json::json!([null]),
240 ] {
241 assert_eq!(format!("{:?}", &v), format!("{:?}", AbbreviateJson(&v)));
242 }
243 }
244}