FormatErrorChain formats an error and its full unwrapped chain in a reading-friendly way. For wrapped errors (fmt.Errorf with %w), each level of the chain is shown on a new indented line. For errors whose message contains newlines (e.g. errors.Join), each line is indented after the first.
(err error)
| 304 | // indented line. For errors whose message contains newlines (e.g. errors.Join), each |
| 305 | // line is indented after the first. |
| 306 | func FormatErrorChain(err error) string { |
| 307 | if err == nil { |
| 308 | return "" |
| 309 | } |
| 310 | |
| 311 | chain := unwrapErrorChain(err) |
| 312 | if len(chain) <= 1 { |
| 313 | return formatMultilineError(err.Error()) |
| 314 | } |
| 315 | |
| 316 | var sb strings.Builder |
| 317 | sb.WriteString(applyStyle(styles.Error, "✗ ")) |
| 318 | sb.WriteString(chain[0]) |
| 319 | for _, msg := range chain[1:] { |
| 320 | // Each message in the chain may itself contain newlines (e.g. from errors.Join |
| 321 | // nested inside a wrapping error); expand them all with consistent indentation. |
| 322 | for line := range strings.SplitSeq(msg, "\n") { |
| 323 | if line != "" { |
| 324 | sb.WriteString("\n ") |
| 325 | sb.WriteString(line) |
| 326 | } |
| 327 | } |
| 328 | } |
| 329 | return sb.String() |
| 330 | } |
| 331 | |
| 332 | // unwrapErrorChain walks the error chain via errors.Unwrap and returns a slice of |
| 333 | // individual message contributions, from outermost to innermost. Each entry contains |