blob: 5c9b67d5379ef4b007bf7aba3ed300b34cd32153 [file] [log] [blame]
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +00001// Copyright (c) 2019 Uber Technologies, Inc.
Don Newton7577f072020-01-06 12:41:11 -05002//
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 {
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +000057// fmt.Println("The following errors occurred:", errors)
Don Newton7577f072020-01-06 12:41:11 -050058// }
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
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000133// error is composed of. If the error is nil, a nil slice is returned.
Don Newton7577f072020-01-06 12:41:11 -0500134//
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}
David K. Bainbridgee05cf0c2021-08-19 03:16:50 +0000400
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}