buildTraceTree builds a trace tree from an error. All errors connected to the given error are considered part of its trace except: if a multi-error is found, a separate trace is built from each of its errors and they're all considered children of this error.
(err error)
| 40 | // a separate trace is built from each of its errors |
| 41 | // and they're all considered children of this error. |
| 42 | func buildTraceTree(err error) traceTree { |
| 43 | current := traceTree{Err: err} |
| 44 | loop: |
| 45 | for { |
| 46 | if frame, inner, ok := UnwrapFrame(err); ok { |
| 47 | current.Trace = append(current.Trace, frame) |
| 48 | err = inner |
| 49 | continue |
| 50 | } |
| 51 | |
| 52 | // We unwrap errors manually instead of using errors.As |
| 53 | // because we don't want to accidentally skip over multi-errors |
| 54 | // or interpret them as part of a single error chain. |
| 55 | switch x := err.(type) { |
| 56 | case interface{ Unwrap() error }: |
| 57 | err = x.Unwrap() |
| 58 | |
| 59 | case interface{ Unwrap() []error }: |
| 60 | // Encountered a multi-error. |
| 61 | // Everything else is a child of current. |
| 62 | errs := x.Unwrap() |
| 63 | current.Children = make([]traceTree, 0, len(errs)) |
| 64 | for _, err := range errs { |
| 65 | current.Children = append(current.Children, buildTraceTree(err)) |
| 66 | } |
| 67 | |
| 68 | break loop |
| 69 | |
| 70 | default: |
| 71 | // Reached a terminal error. |
| 72 | break loop |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | slices.Reverse(current.Trace) |
| 77 | return current |
| 78 | } |
| 79 | |
| 80 | func writeTree(w io.Writer, tree traceTree) error { |
| 81 | return (&treeWriter{W: w}).WriteTree(tree) |