1// Copyright (c) 2017 Uber Technologies, Inc. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a copy 4// of this software and associated documentation files (the "Software"), to deal 5// in the Software without restriction, including without limitation the rights 6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7// copies of the Software, and to permit persons to whom the Software is 8// furnished to do so, subject to the following conditions: 9// 10// The above copyright notice and this permission notice shall be included in 11// all copies or substantial portions of the Software. 12// 13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19// THE SOFTWARE. 20 21// Package multierr allows combining one or more errors together. 22// 23// Overview 24// 25// Errors can be combined with the use of the Combine function. 26// 27// multierr.Combine( 28// reader.Close(), 29// writer.Close(), 30// conn.Close(), 31// ) 32// 33// If only two errors are being combined, the Append function may be used 34// instead. 35// 36// err = multierr.Combine(reader.Close(), writer.Close()) 37// 38// This makes it possible to record resource cleanup failures from deferred 39// blocks with the help of named return values. 40// 41// func sendRequest(req Request) (err error) { 42// conn, err := openConnection() 43// if err != nil { 44// return err 45// } 46// defer func() { 47// err = multierr.Append(err, conn.Close()) 48// }() 49// // ... 50// } 51// 52// The underlying list of errors for a returned error object may be retrieved 53// with the Errors function. 54// 55// errors := multierr.Errors(err) 56// if len(errors) > 0 { 57// fmt.Println("The following errors occurred:") 58// } 59// 60// Advanced Usage 61// 62// Errors returned by Combine and Append MAY implement the following 63// interface. 64// 65// type errorGroup interface { 66// // Returns a slice containing the underlying list of errors. 67// // 68// // This slice MUST NOT be modified by the caller. 69// Errors() []error 70// } 71// 72// Note that if you need access to list of errors behind a multierr error, you 73// should prefer using the Errors function. That said, if you need cheap 74// read-only access to the underlying errors slice, you can attempt to cast 75// the error to this interface. You MUST handle the failure case gracefully 76// because errors returned by Combine and Append are not guaranteed to 77// implement this interface. 78// 79// var errors []error 80// group, ok := err.(errorGroup) 81// if ok { 82// errors = group.Errors() 83// } else { 84// errors = []error{err} 85// } 86package multierr // import "go.uber.org/multierr" 87 88import ( 89 "bytes" 90 "fmt" 91 "io" 92 "strings" 93 "sync" 94 95 "go.uber.org/atomic" 96) 97 98var ( 99 // Separator for single-line error messages. 100 _singlelineSeparator = []byte("; ") 101 102 _newline = []byte("\n") 103 104 // Prefix for multi-line messages 105 _multilinePrefix = []byte("the following errors occurred:") 106 107 // Prefix for the first and following lines of an item in a list of 108 // multi-line error messages. 109 // 110 // For example, if a single item is: 111 // 112 // foo 113 // bar 114 // 115 // It will become, 116 // 117 // - foo 118 // bar 119 _multilineSeparator = []byte("\n - ") 120 _multilineIndent = []byte(" ") 121) 122 123// _bufferPool is a pool of bytes.Buffers. 124var _bufferPool = sync.Pool{ 125 New: func() interface{} { 126 return &bytes.Buffer{} 127 }, 128} 129 130type errorGroup interface { 131 Errors() []error 132} 133 134// Errors returns a slice containing zero or more errors that the supplied 135// error is composed of. If the error is nil, the returned slice is empty. 136// 137// err := multierr.Append(r.Close(), w.Close()) 138// errors := multierr.Errors(err) 139// 140// If the error is not composed of other errors, the returned slice contains 141// just the error that was passed in. 142// 143// Callers of this function are free to modify the returned slice. 144func Errors(err error) []error { 145 if err == nil { 146 return nil 147 } 148 149 // Note that we're casting to multiError, not errorGroup. Our contract is 150 // that returned errors MAY implement errorGroup. Errors, however, only 151 // has special behavior for multierr-specific error objects. 152 // 153 // This behavior can be expanded in the future but I think it's prudent to 154 // start with as little as possible in terms of contract and possibility 155 // of misuse. 156 eg, ok := err.(*multiError) 157 if !ok { 158 return []error{err} 159 } 160 161 errors := eg.Errors() 162 result := make([]error, len(errors)) 163 copy(result, errors) 164 return result 165} 166 167// multiError is an error that holds one or more errors. 168// 169// An instance of this is guaranteed to be non-empty and flattened. That is, 170// none of the errors inside multiError are other multiErrors. 171// 172// multiError formats to a semi-colon delimited list of error messages with 173// %v and with a more readable multi-line format with %+v. 174type multiError struct { 175 copyNeeded atomic.Bool 176 errors []error 177} 178 179var _ errorGroup = (*multiError)(nil) 180 181// Errors returns the list of underlying errors. 182// 183// This slice MUST NOT be modified. 184func (merr *multiError) Errors() []error { 185 if merr == nil { 186 return nil 187 } 188 return merr.errors 189} 190 191func (merr *multiError) Error() string { 192 if merr == nil { 193 return "" 194 } 195 196 buff := _bufferPool.Get().(*bytes.Buffer) 197 buff.Reset() 198 199 merr.writeSingleline(buff) 200 201 result := buff.String() 202 _bufferPool.Put(buff) 203 return result 204} 205 206func (merr *multiError) Format(f fmt.State, c rune) { 207 if c == 'v' && f.Flag('+') { 208 merr.writeMultiline(f) 209 } else { 210 merr.writeSingleline(f) 211 } 212} 213 214func (merr *multiError) writeSingleline(w io.Writer) { 215 first := true 216 for _, item := range merr.errors { 217 if first { 218 first = false 219 } else { 220 w.Write(_singlelineSeparator) 221 } 222 io.WriteString(w, item.Error()) 223 } 224} 225 226func (merr *multiError) writeMultiline(w io.Writer) { 227 w.Write(_multilinePrefix) 228 for _, item := range merr.errors { 229 w.Write(_multilineSeparator) 230 writePrefixLine(w, _multilineIndent, fmt.Sprintf("%+v", item)) 231 } 232} 233 234// Writes s to the writer with the given prefix added before each line after 235// the first. 236func writePrefixLine(w io.Writer, prefix []byte, s string) { 237 first := true 238 for len(s) > 0 { 239 if first { 240 first = false 241 } else { 242 w.Write(prefix) 243 } 244 245 idx := strings.IndexByte(s, '\n') 246 if idx < 0 { 247 idx = len(s) - 1 248 } 249 250 io.WriteString(w, s[:idx+1]) 251 s = s[idx+1:] 252 } 253} 254 255type inspectResult struct { 256 // Number of top-level non-nil errors 257 Count int 258 259 // Total number of errors including multiErrors 260 Capacity int 261 262 // Index of the first non-nil error in the list. Value is meaningless if 263 // Count is zero. 264 FirstErrorIdx int 265 266 // Whether the list contains at least one multiError 267 ContainsMultiError bool 268} 269 270// Inspects the given slice of errors so that we can efficiently allocate 271// space for it. 272func inspect(errors []error) (res inspectResult) { 273 first := true 274 for i, err := range errors { 275 if err == nil { 276 continue 277 } 278 279 res.Count++ 280 if first { 281 first = false 282 res.FirstErrorIdx = i 283 } 284 285 if merr, ok := err.(*multiError); ok { 286 res.Capacity += len(merr.errors) 287 res.ContainsMultiError = true 288 } else { 289 res.Capacity++ 290 } 291 } 292 return 293} 294 295// fromSlice converts the given list of errors into a single error. 296func fromSlice(errors []error) error { 297 res := inspect(errors) 298 switch res.Count { 299 case 0: 300 return nil 301 case 1: 302 // only one non-nil entry 303 return errors[res.FirstErrorIdx] 304 case len(errors): 305 if !res.ContainsMultiError { 306 // already flat 307 return &multiError{errors: errors} 308 } 309 } 310 311 nonNilErrs := make([]error, 0, res.Capacity) 312 for _, err := range errors[res.FirstErrorIdx:] { 313 if err == nil { 314 continue 315 } 316 317 if nested, ok := err.(*multiError); ok { 318 nonNilErrs = append(nonNilErrs, nested.errors...) 319 } else { 320 nonNilErrs = append(nonNilErrs, err) 321 } 322 } 323 324 return &multiError{errors: nonNilErrs} 325} 326 327// Combine combines the passed errors into a single error. 328// 329// If zero arguments were passed or if all items are nil, a nil error is 330// returned. 331// 332// Combine(nil, nil) // == nil 333// 334// If only a single error was passed, it is returned as-is. 335// 336// Combine(err) // == err 337// 338// Combine skips over nil arguments so this function may be used to combine 339// together errors from operations that fail independently of each other. 340// 341// multierr.Combine( 342// reader.Close(), 343// writer.Close(), 344// pipe.Close(), 345// ) 346// 347// If any of the passed errors is a multierr error, it will be flattened along 348// with the other errors. 349// 350// multierr.Combine(multierr.Combine(err1, err2), err3) 351// // is the same as 352// multierr.Combine(err1, err2, err3) 353// 354// The returned error formats into a readable multi-line error message if 355// formatted with %+v. 356// 357// fmt.Sprintf("%+v", multierr.Combine(err1, err2)) 358func Combine(errors ...error) error { 359 return fromSlice(errors) 360} 361 362// Append appends the given errors together. Either value may be nil. 363// 364// This function is a specialization of Combine for the common case where 365// there are only two errors. 366// 367// err = multierr.Append(reader.Close(), writer.Close()) 368// 369// The following pattern may also be used to record failure of deferred 370// operations without losing information about the original error. 371// 372// func doSomething(..) (err error) { 373// f := acquireResource() 374// defer func() { 375// err = multierr.Append(err, f.Close()) 376// }() 377func Append(left error, right error) error { 378 switch { 379 case left == nil: 380 return right 381 case right == nil: 382 return left 383 } 384 385 if _, ok := right.(*multiError); !ok { 386 if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) { 387 // Common case where the error on the left is constantly being 388 // appended to. 389 errs := append(l.errors, right) 390 return &multiError{errors: errs} 391 } else if !ok { 392 // Both errors are single errors. 393 return &multiError{errors: []error{left, right}} 394 } 395 } 396 397 // Either right or both, left and right, are multiErrors. Rely on usual 398 // expensive logic. 399 errors := [2]error{left, right} 400 return fromSlice(errors[0:]) 401} 402