github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/utils/retry/multi_error.go (about) 1 package retry 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 ) 8 9 // NewSizedError returns an multiple error which holds at most size errors. 10 // The SizedError implementation is copied from github.com/jxskiss/errors 11 // to remove dependency of the package and for better compatibility for 12 // future go versions. 13 func NewSizedError(size int) *SizedError { 14 return &SizedError{ 15 errs: make([]error, size), 16 size: size, 17 } 18 } 19 20 type SizedError struct { 21 errs []error 22 size int 23 count int 24 } 25 26 func (E *SizedError) Append(errs ...error) { 27 for _, err := range errs { 28 if err != nil { 29 E.errs[E.count%E.size] = err 30 E.count++ 31 } 32 } 33 } 34 35 func (E *SizedError) Error() string { 36 if E == nil || E.count == 0 { 37 return "<nil>" 38 } 39 var buf bytes.Buffer 40 var first = true 41 for _, err := range E.Errors() { 42 if first { 43 first = false 44 } else { 45 buf.Write(_singlelineSeparator) 46 } 47 buf.WriteString(err.Error()) 48 } 49 return buf.String() 50 } 51 52 func (E *SizedError) ErrOrNil() error { 53 if E == nil || E.count == 0 { 54 return nil 55 } 56 return E 57 } 58 59 // Errors returns the errors as a slice in reversed order, if the underlying 60 // errors are more than size, only size errors will be returned, plus an 61 // additional error indicates the omitted error count. 62 func (E *SizedError) Errors() (errors []error) { 63 if E.count == 0 { 64 return nil 65 } 66 if E.count <= E.size { 67 errors = make([]error, 0, E.count) 68 for i := E.count - 1; i >= 0; i-- { 69 errors = append(errors, E.errs[i]) 70 } 71 return errors 72 } 73 errors = make([]error, 0, E.count+1) 74 for i := E.count%E.size - 1; i >= 0; i-- { 75 errors = append(errors, E.errs[i]) 76 } 77 for i := E.size - 1; i >= E.count%E.size; i-- { 78 errors = append(errors, E.errs[i]) 79 } 80 errors = append(errors, fmt.Errorf("and %d more errors omitted", E.count-E.size)) 81 return errors 82 } 83 84 func (E *SizedError) Format(f fmt.State, c rune) { 85 if c == 'v' && f.Flag('+') { 86 f.Write(formatMultiLine(E.Errors())) 87 } else { 88 f.Write(formatSingleLine(E.Errors())) 89 } 90 } 91 92 var ( 93 // Separator for single-line error messages. 94 _singlelineSeparator = []byte("; ") 95 96 // Prefix for multi-line messages 97 _multilinePrefix = []byte("the following errors occurred:") 98 99 // Prefix for the first and following lines of an item in a list of 100 // multi-line error messages. 101 // 102 // For example, if a single item is: 103 // 104 // foo 105 // bar 106 // 107 // It will become, 108 // 109 // - foo 110 // bar 111 _multilineSeparator = []byte("\n - ") 112 _multilineIndent = []byte(" ") 113 ) 114 115 func formatSingleLine(errs []error) []byte { 116 var buf bytes.Buffer 117 var first = true 118 for _, err := range errs { 119 if first { 120 first = false 121 } else { 122 buf.Write(_singlelineSeparator) 123 } 124 buf.WriteString(err.Error()) 125 } 126 return buf.Bytes() 127 } 128 129 func formatMultiLine(errs []error) []byte { 130 var buf bytes.Buffer 131 buf.Write(_multilinePrefix) 132 for _, err := range errs { 133 buf.Write(_multilineSeparator) 134 s := fmt.Sprintf("%+v", err) 135 first := true 136 for len(s) > 0 { 137 if first { 138 first = false 139 } else { 140 buf.Write(_multilineIndent) 141 } 142 idx := strings.IndexByte(s, '\n') 143 if idx < 0 { 144 idx = len(s) - 1 145 } 146 buf.WriteString(s[:idx+1]) 147 s = s[idx+1:] 148 } 149 } 150 return buf.Bytes() 151 }