github.com/blend/go-sdk@v1.20240719.1/ex/multi.go (about) 1 /* 2 3 Copyright (c) 2024 - Present. Blend Labs, Inc. All rights reserved 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file. 5 6 */ 7 8 package ex 9 10 import ( 11 "fmt" 12 "strings" 13 ) 14 15 // Append appends errors together, creating a multi-error. 16 func Append(err error, errs ...error) error { 17 if len(errs) == 0 { 18 return err 19 } 20 var all []error 21 if err != nil { 22 if me, ok := err.(Multi); ok { 23 all = me 24 } else { 25 all = append(all, NewWithStackDepth(err, DefaultNewStartDepth+1)) 26 } 27 } 28 for _, extra := range errs { 29 if extra != nil { 30 if _, ok := extra.(Multi); !ok { 31 extra = NewWithStackDepth(extra, DefaultNewStartDepth+1) 32 } 33 all = append(all, extra) 34 } 35 } 36 if len(all) == 0 { 37 return nil 38 } 39 if len(all) == 1 { 40 return all[0] 41 } 42 return Multi(all) 43 } 44 45 // Multi represents an array of errors. 46 type Multi []error 47 48 // Unwrap returns all the errors in the multi error (basically itself) 49 func (m Multi) Unwrap() []error { 50 return m 51 } 52 53 // Error implements error. 54 func (m Multi) Error() string { 55 formatted, _ := m.errorString(10, 5, 0) 56 return formatted 57 } 58 59 // FullError returns the full error message. 60 func (m Multi) FullError() string { 61 formatted, _ := m.errorString(-1, -1, 0) 62 return formatted 63 } 64 65 // errorString returns the error string with a length limit and a depth limit, 66 // along with the total number of errors in the Multi error tree. 67 // -1 means no limit. 68 func (m Multi) errorString(listLengthLimit, depthLimit, depth int) (string, int) { 69 if len(m) == 0 { 70 return "", 0 71 } 72 prefix := "\t" 73 if depthLimit >= 0 && depth+1 > depthLimit { 74 total := countErrors(m) 75 return fmt.Sprintf("%s\n%s... depth limit reached ...", m.header(total), prefix), total 76 } 77 78 total := 0 79 var points []string 80 for i, err := range m { 81 if i == listLengthLimit { 82 expanded := total 83 for _, err := range m[i:] { 84 total += countErrors(err) 85 } 86 points = append(points, fmt.Sprintf("... and %d more", total-expanded)) 87 break 88 } 89 if me, ok := err.(Multi); ok { 90 formatted, count := me.errorString(listLengthLimit, depthLimit, depth+1) 91 points = append(points, fmt.Sprintf("* %s", indent(prefix+prefix, formatted))) 92 total += count 93 continue 94 } 95 points = append(points, fmt.Sprintf("* %s", indent(prefix+prefix, err.Error()))) 96 total++ 97 } 98 99 return fmt.Sprintf("%s\n%s%s", m.header(total), prefix, strings.Join(points, "\n"+prefix)), total 100 } 101 102 func (Multi) header(total int) string { 103 if total == 1 { 104 return "1 error occurred:" 105 } 106 return fmt.Sprintf("%d errors occurred:", total) 107 } 108 109 func countErrors(err error) int { 110 if me, ok := err.(Multi); ok { 111 count := 0 112 for _, err := range me { 113 count += countErrors(err) 114 } 115 return count 116 } 117 return 1 118 } 119 120 // Indent functions from https://git.blendlabs.com/blend/go/blob/ad2d96d6f0d62d9a2a2037945663f44d438f6300/sdk/stringutil/indent.go 121 // to avoid import cycle. 122 123 // indent applies an indent prefix to a given corpus except the first line. 124 func indent(prefix, corpus string) string { 125 return strings.Join(indentLines(prefix, strings.Split(corpus, "\n")), "\n") 126 } 127 128 // indentLines adds a prefix to a given list of strings except the first string. 129 func indentLines(prefix string, corpus []string) []string { 130 for index := 1; index < len(corpus); index++ { 131 corpus[index] = prefix + corpus[index] 132 } 133 return corpus 134 }