1 // Copyright (c) 2017 Uber Technologies, Inc.
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:
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
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
21 // Package multierr allows combining one or more errors together.
25 // Errors can be combined with the use of the Combine function.
33 // If only two errors are being combined, the Append function may be used
36 // err = multierr.Combine(reader.Close(), writer.Close())
38 // This makes it possible to record resource cleanup failures from deferred
39 // blocks with the help of named return values.
41 // func sendRequest(req Request) (err error) {
42 // conn, err := openConnection()
47 // err = multierr.Append(err, conn.Close())
52 // The underlying list of errors for a returned error object may be retrieved
53 // with the Errors function.
55 // errors := multierr.Errors(err)
56 // if len(errors) > 0 {
57 // fmt.Println("The following errors occurred:")
62 // Errors returned by Combine and Append MAY implement the following
65 // type errorGroup interface {
66 // // Returns a slice containing the underlying list of errors.
68 // // This slice MUST NOT be modified by the caller.
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.
80 // group, ok := err.(errorGroup)
82 // errors = group.Errors()
84 // errors = []error{err}
86 package multierr // import "go.uber.org/multierr"
99 // Separator for single-line error messages.
100 _singlelineSeparator = []byte("; ")
102 _newline = []byte("\n")
104 // Prefix for multi-line messages
105 _multilinePrefix = []byte("the following errors occurred:")
107 // Prefix for the first and following lines of an item in a list of
108 // multi-line error messages.
110 // For example, if a single item is:
119 _multilineSeparator = []byte("\n - ")
120 _multilineIndent = []byte(" ")
123 // _bufferPool is a pool of bytes.Buffers.
124 var _bufferPool = sync.Pool{
125 New: func() interface{} {
126 return &bytes.Buffer{}
130 type errorGroup interface {
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.
137 // err := multierr.Append(r.Close(), w.Close())
138 // errors := multierr.Errors(err)
140 // If the error is not composed of other errors, the returned slice contains
141 // just the error that was passed in.
143 // Callers of this function are free to modify the returned slice.
144 func Errors(err error) []error {
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.
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
156 eg, ok := err.(*multiError)
161 errors := eg.Errors()
162 result := make([]error, len(errors))
167 // multiError is an error that holds one or more errors.
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.
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.
174 type multiError struct {
175 copyNeeded atomic.Bool
179 var _ errorGroup = (*multiError)(nil)
181 // Errors returns the list of underlying errors.
183 // This slice MUST NOT be modified.
184 func (merr *multiError) Errors() []error {
191 func (merr *multiError) Error() string {
196 buff := _bufferPool.Get().(*bytes.Buffer)
199 merr.writeSingleline(buff)
201 result := buff.String()
202 _bufferPool.Put(buff)
206 func (merr *multiError) Format(f fmt.State, c rune) {
207 if c == 'v' && f.Flag('+') {
208 merr.writeMultiline(f)
210 merr.writeSingleline(f)
214 func (merr *multiError) writeSingleline(w io.Writer) {
216 for _, item := range merr.errors {
220 w.Write(_singlelineSeparator)
222 io.WriteString(w, item.Error())
226 func (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))
234 // Writes s to the writer with the given prefix added before each line after
236 func writePrefixLine(w io.Writer, prefix []byte, s string) {
245 idx := strings.IndexByte(s, '\n')
250 io.WriteString(w, s[:idx+1])
255 type inspectResult struct {
256 // Number of top-level non-nil errors
259 // Total number of errors including multiErrors
262 // Index of the first non-nil error in the list. Value is meaningless if
266 // Whether the list contains at least one multiError
267 ContainsMultiError bool
270 // Inspects the given slice of errors so that we can efficiently allocate
272 func inspect(errors []error) (res inspectResult) {
274 for i, err := range errors {
282 res.FirstErrorIdx = i
285 if merr, ok := err.(*multiError); ok {
286 res.Capacity += len(merr.errors)
287 res.ContainsMultiError = true
295 // fromSlice converts the given list of errors into a single error.
296 func fromSlice(errors []error) error {
297 res := inspect(errors)
302 // only one non-nil entry
303 return errors[res.FirstErrorIdx]
305 if !res.ContainsMultiError {
307 return &multiError{errors: errors}
311 nonNilErrs := make([]error, 0, res.Capacity)
312 for _, err := range errors[res.FirstErrorIdx:] {
317 if nested, ok := err.(*multiError); ok {
318 nonNilErrs = append(nonNilErrs, nested.errors...)
320 nonNilErrs = append(nonNilErrs, err)
324 return &multiError{errors: nonNilErrs}
327 // Combine combines the passed errors into a single error.
329 // If zero arguments were passed or if all items are nil, a nil error is
332 // Combine(nil, nil) // == nil
334 // If only a single error was passed, it is returned as-is.
336 // Combine(err) // == err
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.
347 // If any of the passed errors is a multierr error, it will be flattened along
348 // with the other errors.
350 // multierr.Combine(multierr.Combine(err1, err2), err3)
352 // multierr.Combine(err1, err2, err3)
354 // The returned error formats into a readable multi-line error message if
355 // formatted with %+v.
357 // fmt.Sprintf("%+v", multierr.Combine(err1, err2))
358 func Combine(errors ...error) error {
359 return fromSlice(errors)
362 // Append appends the given errors together. Either value may be nil.
364 // This function is a specialization of Combine for the common case where
365 // there are only two errors.
367 // err = multierr.Append(reader.Close(), writer.Close())
369 // The following pattern may also be used to record failure of deferred
370 // operations without losing information about the original error.
372 // func doSomething(..) (err error) {
373 // f := acquireResource()
375 // err = multierr.Append(err, f.Close())
377 func Append(left error, right error) error {
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
389 errs := append(l.errors, right)
390 return &multiError{errors: errs}
392 // Both errors are single errors.
393 return &multiError{errors: []error{left, right}}
397 // Either right or both, left and right, are multiErrors. Rely on usual
399 errors := [2]error{left, right}
400 return fromSlice(errors[0:])