1use core::{
8 fmt::{self, Display, Formatter, Write},
9 str::{self, Chars},
10};
11
12use crate::{AsErrTree, ErrTree, ErrTreeDisplay};
13
14impl<E: AsErrTree, const FRONT_MAX: usize> ErrTreeDisplay<E, FRONT_MAX> {
15 pub fn new(tree: E) -> Self {
16 Self(tree)
17 }
18}
19
20impl<E: AsErrTree, const FRONT_MAX: usize> Display for ErrTreeDisplay<E, FRONT_MAX> {
21 #[track_caller]
22 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
23 let mut res = Ok(());
24 self.0.as_err_tree(&mut |tree| {
25 res = fmt_tree::<FRONT_MAX, _, _>(tree, f);
26 });
27 res
28 }
29}
30
31pub(crate) fn fmt_tree<const FRONT_MAX: usize, T, W>(tree: T, f: &mut W) -> fmt::Result
32where
33 T: ErrTreeFormattable,
34 W: fmt::Write + ?Sized,
35{
36 #[cfg(not(feature = "heap_buffer"))]
37 let mut front_lines = [0; FRONT_MAX];
38
39 #[cfg(feature = "heap_buffer")]
40 let mut front_lines = alloc::vec![0; FRONT_MAX].into_boxed_slice();
41
42 #[cfg(all(not(feature = "heap_buffer"), feature = "tracing"))]
43 let mut found_traces: [_; FRONT_MAX] = core::array::from_fn(|_| None);
44
45 #[cfg(all(feature = "heap_buffer", feature = "tracing"))]
46 let mut found_traces = core::iter::repeat_with(|| None)
47 .take(FRONT_MAX)
48 .collect::<alloc::vec::Vec<_>>()
49 .into_boxed_slice();
50
51 ErrTreeFmt::<FRONT_MAX, _> {
52 tree,
53 scratch_fill: 0,
54 front_lines: &mut front_lines,
55
56 #[cfg(feature = "tracing")]
57 found_traces: &mut found_traces,
58 }
59 .fmt(f)
60}
61
62#[cfg(feature = "tracing")]
63pub(crate) struct TraceSpan<T: Eq, CharIter> {
64 pub identifier: T,
65 pub target: CharIter,
66 pub name: CharIter,
67 pub fields: CharIter,
68 pub location: Option<(CharIter, u32)>,
69}
70
71pub(crate) trait ErrTreeFormattable {
72 fn apply_msg<W: fmt::Write>(&self, f: W) -> fmt::Result;
73
74 type Source<'a>: ErrTreeFormattable<TraceSpanId = Self::TraceSpanId>;
75
76 #[allow(unused)]
77 fn sources_empty(&mut self) -> bool;
78
79 fn apply_to_leading_sources<F>(&mut self, func: F) -> fmt::Result
80 where
81 F: FnMut(Self::Source<'_>) -> fmt::Result;
82 fn apply_to_last_source<F>(&mut self, func: F) -> fmt::Result
83 where
84 F: FnMut(Self::Source<'_>) -> fmt::Result;
85 #[cfg(feature = "source_line")]
86 fn has_source_line(&self) -> bool;
87 #[cfg(feature = "source_line")]
88 fn apply_source_line<W: fmt::Write>(&self, f: W) -> fmt::Result;
89
90 #[cfg(feature = "tracing")]
91 fn trace_empty(&self) -> bool;
92
93 type TraceSpanId: Eq;
94 type TraceSpanIter<'a>: IntoIterator<Item = char>;
95
96 #[cfg(feature = "tracing")]
97 fn apply_trace<F>(&self, func: F) -> fmt::Result
98 where
99 F: FnMut(TraceSpan<Self::TraceSpanId, Self::TraceSpanIter<'_>>) -> fmt::Result;
100}
101
102impl<T> ErrTreeFormattable for &mut T
103where
104 T: ErrTreeFormattable,
105{
106 fn apply_msg<W: fmt::Write>(&self, f: W) -> fmt::Result {
107 T::apply_msg(self, f)
108 }
109
110 type Source<'a> = T::Source<'a>;
111 fn sources_empty(&mut self) -> bool {
112 T::sources_empty(self)
113 }
114 fn apply_to_leading_sources<F>(&mut self, func: F) -> fmt::Result
115 where
116 F: FnMut(Self::Source<'_>) -> fmt::Result,
117 {
118 T::apply_to_leading_sources(self, func)
119 }
120 fn apply_to_last_source<F>(&mut self, func: F) -> fmt::Result
121 where
122 F: FnMut(Self::Source<'_>) -> fmt::Result,
123 {
124 T::apply_to_last_source(self, func)
125 }
126
127 #[cfg(feature = "source_line")]
128 fn has_source_line(&self) -> bool {
129 T::has_source_line(self)
130 }
131 #[cfg(feature = "source_line")]
132 fn apply_source_line<W: fmt::Write>(&self, f: W) -> fmt::Result {
133 T::apply_source_line(self, f)
134 }
135
136 #[cfg(feature = "tracing")]
137 fn trace_empty(&self) -> bool {
138 T::trace_empty(self)
139 }
140
141 type TraceSpanId = T::TraceSpanId;
142 type TraceSpanIter<'a> = T::TraceSpanIter<'a>;
143
144 #[cfg(feature = "tracing")]
145 fn apply_trace<F>(&self, func: F) -> fmt::Result
146 where
147 F: FnMut(TraceSpan<Self::TraceSpanId, Self::TraceSpanIter<'_>>) -> fmt::Result,
148 {
149 T::apply_trace(self, func)
150 }
151}
152
153impl ErrTreeFormattable for ErrTree<'_> {
154 fn apply_msg<W: fmt::Write>(&self, mut f: W) -> fmt::Result {
155 write!(f, "{}", self.inner)
156 }
157
158 type Source<'a> = ErrTree<'a>;
159 fn sources_empty(&mut self) -> bool {
160 self.sources.is_empty()
161 }
162 fn apply_to_leading_sources<F>(&mut self, mut func: F) -> fmt::Result
163 where
164 F: FnMut(Self::Source<'_>) -> fmt::Result,
165 {
166 if let Some(initial_slice) = self.sources.take_stored_and_next() {
167 let mut initial_iter = initial_slice.as_slice().iter().cloned();
168 if let Some(mut source) = initial_iter.next() {
169 for next_source in initial_iter.chain(self.sources.by_ref()) {
170 let mut res = Ok(());
171 source.as_err_tree(&mut |tree| res = (func)(tree));
172 res?;
173 source = next_source
174 }
175 }
176 }
177 Ok(())
178 }
179 fn apply_to_last_source<F>(&mut self, mut func: F) -> fmt::Result
180 where
181 F: FnMut(Self::Source<'_>) -> fmt::Result,
182 {
183 let _ = self.sources.by_ref().last();
184 if let Some(source) = self.sources.take_stored() {
185 let mut res = Ok(());
186 source.as_err_tree(&mut |tree| res = (func)(tree));
187 res?;
188 }
189 Ok(())
190 }
191
192 #[cfg(feature = "source_line")]
193 fn has_source_line(&self) -> bool {
194 self.location.is_some()
195 }
196
197 #[cfg(feature = "source_line")]
198 fn apply_source_line<W: fmt::Write>(&self, mut f: W) -> fmt::Result {
199 if let Some(loc) = self.location {
200 write!(f, "{}", loc)?;
201 }
202 Ok(())
203 }
204
205 #[cfg(feature = "tracing")]
206 fn trace_empty(&self) -> bool {
207 let mut empty = true;
208 if let Some(trace) = &self.trace {
209 trace.with_spans(|_, _| {
210 empty = false;
211 true
212 });
213 }
214 empty
215 }
216
217 #[cfg(not(feature = "tracing"))]
218 type TraceSpanId = ();
219
220 #[cfg(feature = "tracing")]
221 type TraceSpanId = tracing_core::callsite::Identifier;
222
223 type TraceSpanIter<'a> = Chars<'a>;
224
225 #[cfg(feature = "tracing")]
226 fn apply_trace<F>(&self, mut func: F) -> fmt::Result
227 where
228 F: FnMut(TraceSpan<Self::TraceSpanId, Self::TraceSpanIter<'_>>) -> fmt::Result,
229 {
230 if let Some(trace) = &self.trace {
231 let mut res = Ok(());
232 trace.with_spans(|metadata, fields| {
233 res = (func)(TraceSpan {
234 identifier: metadata.callsite(),
235 target: metadata.target().chars(),
236 name: metadata.name().chars(),
237 fields: fields.chars(),
238 location: metadata
239 .file()
240 .and_then(|file| metadata.line().map(|line| (file.chars(), line))),
241 });
242 res.is_ok()
243 });
244 res
245 } else {
246 Ok(())
247 }
248 }
249}
250
251pub(crate) struct ErrTreeFmt<'a, const FRONT_MAX: usize, T: ErrTreeFormattable> {
252 pub tree: T,
253 pub scratch_fill: usize,
254 pub front_lines: &'a mut [u8],
256
257 #[cfg(feature = "tracing")]
258 pub found_traces: &'a mut [Option<T::TraceSpanId>],
259}
260
261#[cfg_attr(coverage, coverage(off))]
263const fn max_const(lhs: usize, rhs: usize) -> usize {
264 if lhs >= rhs {
265 lhs
266 } else {
267 rhs
268 }
269}
270
271const CONTINUING: &str = "│ ";
272const DANGLING: &str = " ";
273const MAX_CELL_LEN: usize = max_const(CONTINUING.len(), DANGLING.len());
274
275impl<const FRONT_MAX: usize, T: ErrTreeFormattable> ErrTreeFmt<'_, FRONT_MAX, T> {
276 #[inline]
278 fn front_lines_str(front_lines: &[u8], scratch_fill: usize) -> &str {
279 str::from_utf8(&front_lines[..scratch_fill])
280 .expect("All characters are static and guaranteed to be valid UTF-8")
281 }
282
283 #[inline]
285 fn write_front_lines<W>(front_lines: &[u8], f: &mut W, scratch_fill: usize) -> fmt::Result
286 where
287 W: fmt::Write + ?Sized,
288 {
289 f.write_char('\n')?;
290 f.write_str(Self::front_lines_str(front_lines, scratch_fill))
291 }
292
293 #[inline]
295 fn add_front_line(front_lines: &mut [u8], last: bool, scratch_fill: usize) {
296 let chars: &str = if last { DANGLING } else { CONTINUING };
297
298 front_lines[scratch_fill..scratch_fill + chars.len()].copy_from_slice(chars.as_bytes());
299 }
300 #[cfg(feature = "tracing")]
301 fn tracing_after(&self) -> bool {
303 !self.tree.trace_empty()
304 }
305
306 #[cfg(not(feature = "tracing"))]
307 fn tracing_after(&self) -> bool {
308 false
309 }
310
311 #[cfg(feature = "source_line")]
312 fn source_line<W>(&mut self, f: &mut W, tracing_after: bool) -> fmt::Result
313 where
314 W: fmt::Write + ?Sized,
315 {
316 if self.tree.has_source_line() {
317 Self::write_front_lines(self.front_lines, f, self.scratch_fill)?;
318
319 if !tracing_after && self.tree.sources_empty() {
320 f.write_str("╰─ ")?;
321 } else {
322 f.write_str("├─ ")?;
323 }
324 if cfg!(feature = "unix_color") {
325 f.write_str("at \x1b[3m")?;
326 self.tree.apply_source_line(&mut *f)?;
327 f.write_str("\x1b[0m")?;
328 } else {
329 f.write_str("at ")?;
330 self.tree.apply_source_line(f)?;
331 }
332 }
333
334 Ok(())
335 }
336
337 #[cfg(feature = "tracing")]
339 fn tracing_field_fmt<I, W>(
340 f: &mut W,
341 front_lines: &[u8],
342 fields: I,
343 scratch_fill: usize,
344 ) -> fmt::Result
345 where
346 I: IntoIterator<Item = char>,
347 W: fmt::Write + ?Sized,
348 {
349 let mut depth = 0;
350 let mut in_quote = false;
351
352 const START_CHARS: [char; 3] = ['{', '[', '('];
353 const END_CHARS: [char; 3] = ['}', ']', ')'];
354 const ESC: char = '\\';
355
356 let push_front = |f: &mut W, depth| {
357 Self::write_front_lines(front_lines, f, scratch_fill)?;
358 f.write_str("│ ")?;
359 for _ in 0..depth {
360 f.write_str(" ")?;
361 }
362 Ok(())
363 };
364
365 push_front(f, depth)?;
366 let mut prev = '\0';
367 for c in fields {
368 let mut space_except = false;
369
370 if in_quote {
371 if prev == '"' {
372 in_quote = false;
373 if c == ' ' {
374 space_except = true;
375 }
376 }
377 } else {
378 match prev {
379 x if START_CHARS.contains(&x) => {
380 depth += 1;
381 push_front(f, depth)?;
382 if c == ' ' {
383 space_except = true;
384 }
385 }
386 ',' => {
387 push_front(f, depth)?;
388 if c == ' ' {
389 space_except = true;
390 }
391 }
392 '"' => in_quote = true,
393 x => {
394 if END_CHARS.contains(&c) {
395 depth -= 1;
396 push_front(f, depth)?;
397 } else if c == ' ' && END_CHARS.contains(&x) {
398 space_except = true;
399 if depth == 0 {
400 push_front(f, depth)?;
401 }
402 }
403 }
404 }
405 }
406
407 prev = if prev == ESC { '\0' } else { c };
409
410 if !space_except {
411 f.write_char(c)?;
412 }
413 }
414
415 Ok(())
416 }
417
418 #[cfg(feature = "tracing")]
419 fn tracing<W>(&mut self, f: &mut W) -> fmt::Result
420 where
421 W: fmt::Write + ?Sized,
422 {
423 if !self.tree.trace_empty() {
424 Self::write_front_lines(self.front_lines, f, self.scratch_fill)?;
425 write!(f, "│")?;
426
427 #[cfg(all(not(feature = "heap_buffer"), feature = "tracing"))]
428 let mut repeated: [_; FRONT_MAX] = core::array::from_fn(|_| None);
429
430 #[cfg(all(feature = "heap_buffer", feature = "tracing"))]
431 let mut repeated = core::iter::repeat_with(|| None)
432 .take(FRONT_MAX)
433 .collect::<alloc::vec::Vec<_>>()
434 .into_boxed_slice();
435
436 let mut repeated_idx = 0;
437
438 self.tree.apply_trace(|trace_span| {
439 let pos_dup = self
440 .found_traces
441 .iter()
442 .take_while(|x| x.is_some())
443 .flatten()
444 .position(|c| *c == trace_span.identifier);
445
446 if let Some(pos_dup) = pos_dup {
447 repeated[repeated_idx] = Some(pos_dup);
448 repeated_idx += 1;
449 } else {
450 let depth = self.found_traces.partition_point(|x| x.is_some());
451 if depth < self.found_traces.len() {
452 self.found_traces[depth] = Some(trace_span.identifier);
453 }
454
455 Self::write_front_lines(self.front_lines, f, self.scratch_fill)?;
456 write!(f, "├─ tracing frame {} => ", depth)?;
457 for c in trace_span.target {
459 f.write_char(c)?
460 }
461 f.write_str("::")?;
462 for c in trace_span.name {
463 f.write_char(c)?
464 }
465
466 let mut fields = trace_span.fields.into_iter().peekable();
467 if fields.peek().is_some() {
468 write!(f, " with")?;
469 Self::tracing_field_fmt(f, self.front_lines, fields, self.scratch_fill)?;
470 }
471
472 if let Some((file, line)) = trace_span.location {
473 Self::write_front_lines(self.front_lines, f, self.scratch_fill)?;
474 f.write_str("│ at ")?;
475 for c in file {
476 f.write_char(c)?
477 }
478 f.write_char(':')?;
479 write!(f, "{line}")?;
480 };
481 };
482
483 Ok(())
484 })?;
485
486 if repeated_idx > 0 {
487 Self::write_front_lines(self.front_lines, f, self.scratch_fill)?;
488 if self.tree.sources_empty() {
489 f.write_str("╰─ ")?;
490 } else {
491 f.write_str("├─ ")?;
492 }
493
494 write!(f, "{} duplicate tracing frame(s): [", repeated_idx)?;
495
496 for idx in 0..repeated_idx - 1 {
497 write!(f, "{}, ", repeated[idx].expect("Previously set as Some"))?;
498 }
499
500 write!(
501 f,
502 "{}]",
503 repeated[repeated_idx - 1].expect("Previously set as Some")
504 )?;
505 }
506 }
507 Ok(())
508 }
509
510 #[allow(unused_mut)]
511 fn fmt<W>(mut self, f: &mut W) -> fmt::Result
512 where
513 W: fmt::Write + ?Sized,
514 {
515 self.tree.apply_msg(LeadingLineFormatter::new(
516 &mut *f,
517 Self::front_lines_str(self.front_lines, self.scratch_fill),
518 ))?;
519
520 #[cfg_attr(
521 not(any(feature = "source_line", feature = "tracing")),
522 expect(unused_variables, reason = "only used to track for a tracing line")
523 )]
524 let tracing_after = self.tracing_after();
525
526 #[cfg(feature = "source_line")]
527 self.source_line(f, tracing_after)?;
528
529 #[cfg(feature = "tracing")]
530 self.tracing(f)?;
531
532 let mut source_fmt =
533 |front_lines: &mut [u8],
534 scratch_fill: usize,
535 #[cfg(feature = "tracing")] found_traces: &mut [Option<T::TraceSpanId>],
536 source: T::Source<'_>,
537 last: bool| {
538 Self::write_front_lines(front_lines, f, scratch_fill)?;
539 f.write_char('│')?;
540 Self::write_front_lines(front_lines, f, scratch_fill)?;
541
542 if last {
543 f.write_str("╰─▶ ")?;
544 } else {
545 f.write_str("├─▶ ")?;
546 }
547
548 let additional_scratch = if last {
549 DANGLING.len()
550 } else {
551 CONTINUING.len()
552 };
553
554 ErrTreeFmt::<FRONT_MAX, _> {
555 tree: source,
556 scratch_fill: scratch_fill + additional_scratch,
557 front_lines,
558
559 #[cfg(feature = "tracing")]
560 found_traces,
561 }
562 .fmt(f)
563 };
564
565 if self.scratch_fill + MAX_CELL_LEN >= FRONT_MAX {
566 writeln!(f, "{:.<1$}", "", MAX_CELL_LEN)?;
568 } else {
569 Self::add_front_line(self.front_lines, false, self.scratch_fill);
572 self.tree.apply_to_leading_sources(|source| {
573 source_fmt(
574 self.front_lines,
575 self.scratch_fill,
576 #[cfg(feature = "tracing")]
577 self.found_traces,
578 source,
579 false,
580 )
581 })?;
582
583 self.tree.apply_to_last_source(|source| {
584 Self::add_front_line(self.front_lines, true, self.scratch_fill);
585 source_fmt(
586 self.front_lines,
587 self.scratch_fill,
588 #[cfg(feature = "tracing")]
589 self.found_traces,
590 source,
591 true,
592 )
593 })?;
594 };
595
596 Ok(())
597 }
598}
599
600struct LeadingLineFormatter<'a, F> {
602 formatter: F,
603 leading: &'a str,
604}
605
606impl<'a, F> LeadingLineFormatter<'a, F> {
607 pub fn new(formatter: F, leading: &'a str) -> Self {
608 Self { formatter, leading }
609 }
610}
611
612impl<F: Write> Write for LeadingLineFormatter<'_, F> {
613 fn write_str(&mut self, s: &str) -> fmt::Result {
614 if s.chars().all(|c| c != '\n') {
615 self.formatter.write_str(s)?
616 } else {
617 for c in s.chars() {
618 self.write_char(c)?;
619 }
620 }
621 Ok(())
622 }
623
624 fn write_char(&mut self, c: char) -> fmt::Result {
625 self.formatter.write_char(c)?;
626
627 if c == '\n' {
628 self.formatter.write_str(self.leading)?;
629 self.formatter.write_str("│ ")?;
630 }
631
632 Ok(())
633 }
634}