1// Copyright (c) 2019 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.Append(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:", errors) 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 // Prefix for multi-line messages 103 _multilinePrefix = []byte("the following errors occurred:") 104 105 // Prefix for the first and following lines of an item in a list of 106 // multi-line error messages. 107 // 108 // For example, if a single item is: 109 // 110 // foo 111 // bar 112 // 113 // It will become, 114 // 115 // - foo 116 // bar 117 _multilineSeparator = []byte("\n - ") 118 _multilineIndent = []byte(" ") 119) 120 121// _bufferPool is a pool of bytes.Buffers. 122var _bufferPool = sync.Pool{ 123 New: func() interface{} { 124 return &bytes.Buffer{} 125 }, 126} 127 128type errorGroup interface { 129 Errors() []error 130} 131 132// Errors returns a slice containing zero or more errors that the supplied 133// error is composed of. If the error is nil, a nil slice is returned. 134// 135// err := multierr.Append(r.Close(), w.Close()) 136// errors := multierr.Errors(err) 137// 138// If the error is not composed of other errors, the returned slice contains 139// just the error that was passed in. 140// 141// Callers of this function are free to modify the returned slice. 142func Errors(err error) []error { 143 if err == nil { 144 return nil 145 } 146 147 // Note that we're casting to multiError, not errorGroup. Our contract is 148 // that returned errors MAY implement errorGroup. Errors, however, only 149 // has special behavior for multierr-specific error objects. 150 // 151 // This behavior can be expanded in the future but I think it's prudent to 152 // start with as little as possible in terms of contract and possibility 153 // of misuse. 154 eg, ok := err.(*multiError) 155 if !ok { 156 return []error{err} 157 } 158 159 errors := eg.Errors() 160 result := make([]error, len(errors)) 161 copy(result, errors) 162 return result 163} 164 165// multiError is an error that holds one or more errors. 166// 167// An instance of this is guaranteed to be non-empty and flattened. That is, 168// none of the errors inside multiError are other multiErrors. 169// 170// multiError formats to a semi-colon delimited list of error messages with 171// %v and with a more readable multi-line format with %+v. 172type multiError struct { 173 copyNeeded atomic.Bool 174 errors []error 175} 176 177var _ errorGroup = (*multiError)(nil) 178 179// Errors returns the list of underlying errors. 180// 181// This slice MUST NOT be modified. 182func (merr *multiError) Errors() []error { 183 if merr == nil { 184 return nil 185 } 186 return merr.errors 187} 188 189func (merr *multiError) Error() string { 190 if merr == nil { 191 return "" 192 } 193 194 buff := _bufferPool.Get().(*bytes.Buffer) 195 buff.Reset() 196 197 merr.writeSingleline(buff) 198 199 result := buff.String() 200 _bufferPool.Put(buff) 201 return result 202} 203 204func (merr *multiError) Format(f fmt.State, c rune) { 205 if c == 'v' && f.Flag('+') { 206 merr.writeMultiline(f) 207 } else { 208 merr.writeSingleline(f) 209 } 210} 211 212func (merr *multiError) writeSingleline(w io.Writer) { 213 first := true 214 for _, item := range merr.errors { 215 if first { 216 first = false 217 } else { 218 w.Write(_singlelineSeparator) 219 } 220 io.WriteString(w, item.Error()) 221 } 222} 223 224func (merr *multiError) writeMultiline(w io.Writer) { 225 w.Write(_multilinePrefix) 226 for _, item := range merr.errors { 227 w.Write(_multilineSeparator) 228 writePrefixLine(w, _multilineIndent, fmt.Sprintf("%+v", item)) 229 } 230} 231 232// Writes s to the writer with the given prefix added before each line after 233// the first. 234func writePrefixLine(w io.Writer, prefix []byte, s string) { 235 first := true 236 for len(s) > 0 { 237 if first { 238 first = false 239 } else { 240 w.Write(prefix) 241 } 242 243 idx := strings.IndexByte(s, '\n') 244 if idx < 0 { 245 idx = len(s) - 1 246 } 247 248 io.WriteString(w, s[:idx+1]) 249 s = s[idx+1:] 250 } 251} 252 253type inspectResult struct { 254 // Number of top-level non-nil errors 255 Count int 256 257 // Total number of errors including multiErrors 258 Capacity int 259 260 // Index of the first non-nil error in the list. Value is meaningless if 261 // Count is zero. 262 FirstErrorIdx int 263 264 // Whether the list contains at least one multiError 265 ContainsMultiError bool 266} 267 268// Inspects the given slice of errors so that we can efficiently allocate 269// space for it. 270func inspect(errors []error) (res inspectResult) { 271 first := true 272 for i, err := range errors { 273 if err == nil { 274 continue 275 } 276 277 res.Count++ 278 if first { 279 first = false 280 res.FirstErrorIdx = i 281 } 282 283 if merr, ok := err.(*multiError); ok { 284 res.Capacity += len(merr.errors) 285 res.ContainsMultiError = true 286 } else { 287 res.Capacity++ 288 } 289 } 290 return 291} 292 293// fromSlice converts the given list of errors into a single error. 294func fromSlice(errors []error) error { 295 res := inspect(errors) 296 switch res.Count { 297 case 0: 298 return nil 299 case 1: 300 // only one non-nil entry 301 return errors[res.FirstErrorIdx] 302 case len(errors): 303 if !res.ContainsMultiError { 304 // already flat 305 return &multiError{errors: errors} 306 } 307 } 308 309 nonNilErrs := make([]error, 0, res.Capacity) 310 for _, err := range errors[res.FirstErrorIdx:] { 311 if err == nil { 312 continue 313 } 314 315 if nested, ok := err.(*multiError); ok { 316 nonNilErrs = append(nonNilErrs, nested.errors...) 317 } else { 318 nonNilErrs = append(nonNilErrs, err) 319 } 320 } 321 322 return &multiError{errors: nonNilErrs} 323} 324 325// Combine combines the passed errors into a single error. 326// 327// If zero arguments were passed or if all items are nil, a nil error is 328// returned. 329// 330// Combine(nil, nil) // == nil 331// 332// If only a single error was passed, it is returned as-is. 333// 334// Combine(err) // == err 335// 336// Combine skips over nil arguments so this function may be used to combine 337// together errors from operations that fail independently of each other. 338// 339// multierr.Combine( 340// reader.Close(), 341// writer.Close(), 342// pipe.Close(), 343// ) 344// 345// If any of the passed errors is a multierr error, it will be flattened along 346// with the other errors. 347// 348// multierr.Combine(multierr.Combine(err1, err2), err3) 349// // is the same as 350// multierr.Combine(err1, err2, err3) 351// 352// The returned error formats into a readable multi-line error message if 353// formatted with %+v. 354// 355// fmt.Sprintf("%+v", multierr.Combine(err1, err2)) 356func Combine(errors ...error) error { 357 return fromSlice(errors) 358} 359 360// Append appends the given errors together. Either value may be nil. 361// 362// This function is a specialization of Combine for the common case where 363// there are only two errors. 364// 365// err = multierr.Append(reader.Close(), writer.Close()) 366// 367// The following pattern may also be used to record failure of deferred 368// operations without losing information about the original error. 369// 370// func doSomething(..) (err error) { 371// f := acquireResource() 372// defer func() { 373// err = multierr.Append(err, f.Close()) 374// }() 375func Append(left error, right error) error { 376 switch { 377 case left == nil: 378 return right 379 case right == nil: 380 return left 381 } 382 383 if _, ok := right.(*multiError); !ok { 384 if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) { 385 // Common case where the error on the left is constantly being 386 // appended to. 387 errs := append(l.errors, right) 388 return &multiError{errors: errs} 389 } else if !ok { 390 // Both errors are single errors. 391 return &multiError{errors: []error{left, right}} 392 } 393 } 394 395 // Either right or both, left and right, are multiErrors. Rely on usual 396 // expensive logic. 397 errors := [2]error{left, right} 398 return fromSlice(errors[0:]) 399} 400 401// AppendInto appends an error into the destination of an error pointer and 402// returns whether the error being appended was non-nil. 403// 404// var err error 405// multierr.AppendInto(&err, r.Close()) 406// multierr.AppendInto(&err, w.Close()) 407// 408// The above is equivalent to, 409// 410// err := multierr.Append(r.Close(), w.Close()) 411// 412// As AppendInto reports whether the provided error was non-nil, it may be 413// used to build a multierr error in a loop more ergonomically. For example: 414// 415// var err error 416// for line := range lines { 417// var item Item 418// if multierr.AppendInto(&err, parse(line, &item)) { 419// continue 420// } 421// items = append(items, item) 422// } 423// 424// Compare this with a verison that relies solely on Append: 425// 426// var err error 427// for line := range lines { 428// var item Item 429// if parseErr := parse(line, &item); parseErr != nil { 430// err = multierr.Append(err, parseErr) 431// continue 432// } 433// items = append(items, item) 434// } 435func AppendInto(into *error, err error) (errored bool) { 436 if into == nil { 437 // We panic if 'into' is nil. This is not documented above 438 // because suggesting that the pointer must be non-nil may 439 // confuse users into thinking that the error that it points 440 // to must be non-nil. 441 panic("misuse of multierr.AppendInto: into pointer must not be nil") 442 } 443 444 if err == nil { 445 return false 446 } 447 *into = Append(*into, err) 448 return true 449} 450