@@ -102,7 +102,13 @@ func formatErrorInternal(err error, s fmt.State, verb rune, redactableOutput boo
102102 // to enable stack trace de-duplication. This requires a
103103 // post-order traversal. Since we have a linked list, the best we
104104 // can do is a recursion.
105- p .formatRecursive (err , true /* isOutermost */ , true /* withDetail */ )
105+ p .formatRecursive (
106+ err ,
107+ true , /* isOutermost */
108+ true , /* withDetail */
109+ false , /* withDepth */
110+ 0 , /* depth */
111+ )
106112
107113 // We now have all the data, we can render the result.
108114 p .formatEntries (err )
@@ -146,7 +152,13 @@ func formatErrorInternal(err error, s fmt.State, verb rune, redactableOutput boo
146152 // by calling FormatError(), in which case we'd get an infinite
147153 // recursion. So we have no choice but to peel the data
148154 // and then assemble the pieces ourselves.
149- p .formatRecursive (err , true /* isOutermost */ , false /* withDetail */ )
155+ p .formatRecursive (
156+ err ,
157+ true , /* isOutermost */
158+ false , /* withDetail */
159+ false , /* withDepth */
160+ 0 , /* depth */
161+ )
150162 p .formatSingleLineOutput ()
151163 p .finishDisplay (verb )
152164
@@ -195,7 +207,19 @@ func (s *state) formatEntries(err error) {
195207 // Wraps: (N) <details>
196208 //
197209 for i , j := len (s .entries )- 2 , 2 ; i >= 0 ; i , j = i - 1 , j + 1 {
198- fmt .Fprintf (& s .finalBuf , "\n Wraps: (%d)" , j )
210+ s .finalBuf .WriteByte ('\n' )
211+ // Extra indentation starts at depth==2 because the direct
212+ // children of the root error area already printed on separate
213+ // newlines.
214+ for m := 0 ; m < s .entries [i ].depth - 1 ; m += 1 {
215+ if m == s .entries [i ].depth - 2 {
216+ s .finalBuf .WriteString ("└─ " )
217+ } else {
218+ s .finalBuf .WriteByte (' ' )
219+ s .finalBuf .WriteByte (' ' )
220+ }
221+ }
222+ fmt .Fprintf (& s .finalBuf , "Wraps: (%d)" , j )
199223 entry := s .entries [i ]
200224 s .printEntry (entry )
201225 }
@@ -330,12 +354,34 @@ func (s *state) formatSingleLineOutput() {
330354// s.finalBuf is untouched. The conversion of s.entries
331355// to s.finalBuf is done by formatSingleLineOutput() and/or
332356// formatEntries().
333- func (s * state ) formatRecursive (err error , isOutermost , withDetail bool ) {
357+ //
358+ // `withDepth` and `depth` are used to tag subtrees of multi-cause
359+ // errors for added indentation during printing. Once a multi-cause
360+ // error is encountered, all subsequent calls with set `withDepth` to
361+ // true, and increment `depth` during recursion. This information is
362+ // persisted into the generated entries and used later to display the
363+ // error with increased indentation based in the depth.
364+ func (s * state ) formatRecursive (err error , isOutermost , withDetail , withDepth bool , depth int ) int {
334365 cause := UnwrapOnce (err )
366+ numChildren := 0
335367 if cause != nil {
336- // Recurse first.
337- s .formatRecursive (cause , false /*isOutermost*/ , withDetail )
368+ // Recurse first, which populates entries list starting from innermost
369+ // entry. If we've previously seen a multi-cause wrapper, `withDepth`
370+ // will be true, and we'll record the depth below ensuring that extra
371+ // indentation is applied to this inner cause during printing.
372+ // Otherwise, we maintain "straight" vertical formatting by keeping the
373+ // parent callers `withDepth` value of `false` by default.
374+ numChildren += s .formatRecursive (cause , false , withDetail , withDepth , depth + 1 )
375+ }
376+
377+ causes := UnwrapMulti (err )
378+ for _ , c := range causes {
379+ // Override `withDepth` to true for all child entries ensuring they have
380+ // indentation applied during formatting to distinguish them from
381+ // parents.
382+ numChildren += s .formatRecursive (c , false , withDetail , true , depth + 1 )
338383 }
384+ // inserted := len(s.entries) - 1 - startChildren
339385
340386 // Reinitialize the state for this stage of wrapping.
341387 s .wantDetail = withDetail
@@ -355,17 +401,19 @@ func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
355401 bufIsRedactable = true
356402 desiredShortening := v .SafeFormatError ((* safePrinter )(s ))
357403 if desiredShortening == nil {
358- // The error wants to elide the short messages from inner
359- // causes. Do it.
360- s .elideFurtherCauseMsgs ()
404+ // The error wants to elide the short messages from inner causes.
405+ // Read backwards through list of entries up to the number of new
406+ // entries created "under" this one amount and mark `elideShort`
407+ // true.
408+ s .elideShortChildren (numChildren )
361409 }
362410
363411 case Formatter :
364412 desiredShortening := v .FormatError ((* printer )(s ))
365413 if desiredShortening == nil {
366414 // The error wants to elide the short messages from inner
367415 // causes. Do it.
368- s .elideFurtherCauseMsgs ( )
416+ s .elideShortChildren ( numChildren )
369417 }
370418
371419 case fmt.Formatter :
@@ -389,7 +437,7 @@ func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
389437 if elideCauseMsg := s .formatSimple (err , cause ); elideCauseMsg {
390438 // The error wants to elide the short messages from inner
391439 // causes. Do it.
392- s .elideFurtherCauseMsgs ( )
440+ s .elideShortChildren ( numChildren )
393441 }
394442 }
395443
@@ -412,7 +460,7 @@ func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
412460 if desiredShortening == nil {
413461 // The error wants to elide the short messages from inner
414462 // causes. Do it.
415- s .elideFurtherCauseMsgs ( )
463+ s .elideShortChildren ( numChildren )
416464 }
417465 break
418466 }
@@ -421,16 +469,21 @@ func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
421469 // If the error did not implement errors.Formatter nor
422470 // fmt.Formatter, but it is a wrapper, still attempt best effort:
423471 // print what we can at this level.
424- if elideCauseMsg := s .formatSimple (err , cause ); elideCauseMsg {
472+ elideChildren := s .formatSimple (err , cause )
473+ // always elideChildren when dealing with multi-cause errors.
474+ if len (causes ) > 0 {
475+ elideChildren = true
476+ }
477+ if elideChildren {
425478 // The error wants to elide the short messages from inner
426479 // causes. Do it.
427- s .elideFurtherCauseMsgs ( )
480+ s .elideShortChildren ( numChildren )
428481 }
429482 }
430483 }
431484
432485 // Collect the result.
433- entry := s .collectEntry (err , bufIsRedactable )
486+ entry := s .collectEntry (err , bufIsRedactable , withDepth , depth )
434487
435488 // If there's an embedded stack trace, also collect it.
436489 // This will get either a stack from pkg/errors, or ours.
@@ -444,21 +497,22 @@ func (s *state) formatRecursive(err error, isOutermost, withDetail bool) {
444497 // Remember the entry for later rendering.
445498 s .entries = append (s .entries , entry )
446499 s .buf = bytes.Buffer {}
500+
501+ return numChildren + 1
447502}
448503
449- // elideFurtherCauseMsgs sets the `elideShort` field
450- // on all entries added so far to `true`. Because these
451- // entries are added recursively from the innermost
452- // cause outward, we can iterate through all entries
453- // without bound because the caller is guaranteed not
454- // to see entries that it is the causer of.
455- func (s * state ) elideFurtherCauseMsgs () {
456- for i := range s .entries {
457- s .entries [i ].elideShort = true
504+ // elideShortChildren takes a number of entries to set `elideShort` to
505+ // false. The reason a number of entries is needed is that we may be
506+ // eliding a subtree of causes in the case of a multi-cause error. In
507+ // the multi-cause case, we need to know how many of the prior errors
508+ // in the list of entries is a child of this subtree.
509+ func (s * state ) elideShortChildren (newEntries int ) {
510+ for i := 0 ; i < newEntries ; i ++ {
511+ s .entries [len (s .entries )- 1 - i ].elideShort = true
458512 }
459513}
460514
461- func (s * state ) collectEntry (err error , bufIsRedactable bool ) formatEntry {
515+ func (s * state ) collectEntry (err error , bufIsRedactable bool , withDepth bool , depth int ) formatEntry {
462516 entry := formatEntry {err : err }
463517 if s .wantDetail {
464518 // The buffer has been populated as a result of formatting with
@@ -495,6 +549,10 @@ func (s *state) collectEntry(err error, bufIsRedactable bool) formatEntry {
495549 }
496550 }
497551
552+ if withDepth {
553+ entry .depth = depth
554+ }
555+
498556 return entry
499557}
500558
@@ -712,6 +770,11 @@ type formatEntry struct {
712770 // truncated to avoid duplication of entries. This is used to
713771 // display a truncation indicator during verbose rendering.
714772 elidedStackTrace bool
773+
774+ // depth, if positive, represents a nesting depth of this error as
775+ // a causer of others. This is used with verbose printing to
776+ // illustrate the nesting depth for multi-cause error wrappers.
777+ depth int
715778}
716779
717780// String is used for debugging only.
@@ -733,6 +796,12 @@ func (s *state) Write(b []byte) (n int, err error) {
733796
734797 for i , c := range b {
735798 if c == '\n' {
799+ //if s.needNewline > 0 {
800+ // for i := 0; i < s.needNewline-1; i++ {
801+ // s.buf.Write(detailSep[:len(sep)-1])
802+ // }
803+ // s.needNewline = 0
804+ //}
736805 // Flush all the bytes seen so far.
737806 s .buf .Write (b [k :i ])
738807 // Don't print the newline itself; instead, prepare the state so
@@ -762,6 +831,11 @@ func (s *state) Write(b []byte) (n int, err error) {
762831 s .notEmpty = true
763832 }
764833 }
834+ //if s.needNewline > 0 {
835+ // for i := 0; i < s.needNewline-1; i++ {
836+ // s.buf.Write(detailSep[:len(sep)-1])
837+ // }
838+ //}
765839 s .buf .Write (b [k :])
766840 return len (b ), nil
767841}
@@ -788,6 +862,9 @@ func (p *state) switchOver() {
788862 p .buf = bytes.Buffer {}
789863 p .notEmpty = false
790864 p .hasDetail = true
865+
866+ // One of the newlines is accounted for in the switch over.
867+ // p.needNewline -= 1
791868}
792869
793870func (s * printer ) Detail () bool {
0 commit comments