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