blob: de6ce4736c8caf673467197120d07036ff04c2a5 [file] [log] [blame]
Scott Bakereee8dd82019-09-24 12:52:34 -07001// 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}