402 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			402 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright (c) 2017 Uber Technologies, Inc.
 | |
| //
 | |
| // Permission is hereby granted, free of charge, to any person obtaining a copy
 | |
| // of this software and associated documentation files (the "Software"), to deal
 | |
| // in the Software without restriction, including without limitation the rights
 | |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | |
| // copies of the Software, and to permit persons to whom the Software is
 | |
| // furnished to do so, subject to the following conditions:
 | |
| //
 | |
| // The above copyright notice and this permission notice shall be included in
 | |
| // all copies or substantial portions of the Software.
 | |
| //
 | |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | |
| // THE SOFTWARE.
 | |
| 
 | |
| // Package multierr allows combining one or more errors together.
 | |
| //
 | |
| // Overview
 | |
| //
 | |
| // Errors can be combined with the use of the Combine function.
 | |
| //
 | |
| // 	multierr.Combine(
 | |
| // 		reader.Close(),
 | |
| // 		writer.Close(),
 | |
| // 		conn.Close(),
 | |
| // 	)
 | |
| //
 | |
| // If only two errors are being combined, the Append function may be used
 | |
| // instead.
 | |
| //
 | |
| // 	err = multierr.Combine(reader.Close(), writer.Close())
 | |
| //
 | |
| // This makes it possible to record resource cleanup failures from deferred
 | |
| // blocks with the help of named return values.
 | |
| //
 | |
| // 	func sendRequest(req Request) (err error) {
 | |
| // 		conn, err := openConnection()
 | |
| // 		if err != nil {
 | |
| // 			return err
 | |
| // 		}
 | |
| // 		defer func() {
 | |
| // 			err = multierr.Append(err, conn.Close())
 | |
| // 		}()
 | |
| // 		// ...
 | |
| // 	}
 | |
| //
 | |
| // The underlying list of errors for a returned error object may be retrieved
 | |
| // with the Errors function.
 | |
| //
 | |
| // 	errors := multierr.Errors(err)
 | |
| // 	if len(errors) > 0 {
 | |
| // 		fmt.Println("The following errors occurred:")
 | |
| // 	}
 | |
| //
 | |
| // Advanced Usage
 | |
| //
 | |
| // Errors returned by Combine and Append MAY implement the following
 | |
| // interface.
 | |
| //
 | |
| // 	type errorGroup interface {
 | |
| // 		// Returns a slice containing the underlying list of errors.
 | |
| // 		//
 | |
| // 		// This slice MUST NOT be modified by the caller.
 | |
| // 		Errors() []error
 | |
| // 	}
 | |
| //
 | |
| // Note that if you need access to list of errors behind a multierr error, you
 | |
| // should prefer using the Errors function. That said, if you need cheap
 | |
| // read-only access to the underlying errors slice, you can attempt to cast
 | |
| // the error to this interface. You MUST handle the failure case gracefully
 | |
| // because errors returned by Combine and Append are not guaranteed to
 | |
| // implement this interface.
 | |
| //
 | |
| // 	var errors []error
 | |
| // 	group, ok := err.(errorGroup)
 | |
| // 	if ok {
 | |
| // 		errors = group.Errors()
 | |
| // 	} else {
 | |
| // 		errors = []error{err}
 | |
| // 	}
 | |
| package multierr // import "go.uber.org/multierr"
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	"go.uber.org/atomic"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	// Separator for single-line error messages.
 | |
| 	_singlelineSeparator = []byte("; ")
 | |
| 
 | |
| 	_newline = []byte("\n")
 | |
| 
 | |
| 	// Prefix for multi-line messages
 | |
| 	_multilinePrefix = []byte("the following errors occurred:")
 | |
| 
 | |
| 	// Prefix for the first and following lines of an item in a list of
 | |
| 	// multi-line error messages.
 | |
| 	//
 | |
| 	// For example, if a single item is:
 | |
| 	//
 | |
| 	// 	foo
 | |
| 	// 	bar
 | |
| 	//
 | |
| 	// It will become,
 | |
| 	//
 | |
| 	// 	 -  foo
 | |
| 	// 	    bar
 | |
| 	_multilineSeparator = []byte("\n -  ")
 | |
| 	_multilineIndent    = []byte("    ")
 | |
| )
 | |
| 
 | |
| // _bufferPool is a pool of bytes.Buffers.
 | |
| var _bufferPool = sync.Pool{
 | |
| 	New: func() interface{} {
 | |
| 		return &bytes.Buffer{}
 | |
| 	},
 | |
| }
 | |
| 
 | |
| type errorGroup interface {
 | |
| 	Errors() []error
 | |
| }
 | |
| 
 | |
| // Errors returns a slice containing zero or more errors that the supplied
 | |
| // error is composed of. If the error is nil, the returned slice is empty.
 | |
| //
 | |
| // 	err := multierr.Append(r.Close(), w.Close())
 | |
| // 	errors := multierr.Errors(err)
 | |
| //
 | |
| // If the error is not composed of other errors, the returned slice contains
 | |
| // just the error that was passed in.
 | |
| //
 | |
| // Callers of this function are free to modify the returned slice.
 | |
| func Errors(err error) []error {
 | |
| 	if err == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	// Note that we're casting to multiError, not errorGroup. Our contract is
 | |
| 	// that returned errors MAY implement errorGroup. Errors, however, only
 | |
| 	// has special behavior for multierr-specific error objects.
 | |
| 	//
 | |
| 	// This behavior can be expanded in the future but I think it's prudent to
 | |
| 	// start with as little as possible in terms of contract and possibility
 | |
| 	// of misuse.
 | |
| 	eg, ok := err.(*multiError)
 | |
| 	if !ok {
 | |
| 		return []error{err}
 | |
| 	}
 | |
| 
 | |
| 	errors := eg.Errors()
 | |
| 	result := make([]error, len(errors))
 | |
| 	copy(result, errors)
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| // multiError is an error that holds one or more errors.
 | |
| //
 | |
| // An instance of this is guaranteed to be non-empty and flattened. That is,
 | |
| // none of the errors inside multiError are other multiErrors.
 | |
| //
 | |
| // multiError formats to a semi-colon delimited list of error messages with
 | |
| // %v and with a more readable multi-line format with %+v.
 | |
| type multiError struct {
 | |
| 	copyNeeded atomic.Bool
 | |
| 	errors     []error
 | |
| }
 | |
| 
 | |
| var _ errorGroup = (*multiError)(nil)
 | |
| 
 | |
| // Errors returns the list of underlying errors.
 | |
| //
 | |
| // This slice MUST NOT be modified.
 | |
| func (merr *multiError) Errors() []error {
 | |
| 	if merr == nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return merr.errors
 | |
| }
 | |
| 
 | |
| func (merr *multiError) Error() string {
 | |
| 	if merr == nil {
 | |
| 		return ""
 | |
| 	}
 | |
| 
 | |
| 	buff := _bufferPool.Get().(*bytes.Buffer)
 | |
| 	buff.Reset()
 | |
| 
 | |
| 	merr.writeSingleline(buff)
 | |
| 
 | |
| 	result := buff.String()
 | |
| 	_bufferPool.Put(buff)
 | |
| 	return result
 | |
| }
 | |
| 
 | |
| func (merr *multiError) Format(f fmt.State, c rune) {
 | |
| 	if c == 'v' && f.Flag('+') {
 | |
| 		merr.writeMultiline(f)
 | |
| 	} else {
 | |
| 		merr.writeSingleline(f)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (merr *multiError) writeSingleline(w io.Writer) {
 | |
| 	first := true
 | |
| 	for _, item := range merr.errors {
 | |
| 		if first {
 | |
| 			first = false
 | |
| 		} else {
 | |
| 			w.Write(_singlelineSeparator)
 | |
| 		}
 | |
| 		io.WriteString(w, item.Error())
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (merr *multiError) writeMultiline(w io.Writer) {
 | |
| 	w.Write(_multilinePrefix)
 | |
| 	for _, item := range merr.errors {
 | |
| 		w.Write(_multilineSeparator)
 | |
| 		writePrefixLine(w, _multilineIndent, fmt.Sprintf("%+v", item))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Writes s to the writer with the given prefix added before each line after
 | |
| // the first.
 | |
| func writePrefixLine(w io.Writer, prefix []byte, s string) {
 | |
| 	first := true
 | |
| 	for len(s) > 0 {
 | |
| 		if first {
 | |
| 			first = false
 | |
| 		} else {
 | |
| 			w.Write(prefix)
 | |
| 		}
 | |
| 
 | |
| 		idx := strings.IndexByte(s, '\n')
 | |
| 		if idx < 0 {
 | |
| 			idx = len(s) - 1
 | |
| 		}
 | |
| 
 | |
| 		io.WriteString(w, s[:idx+1])
 | |
| 		s = s[idx+1:]
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type inspectResult struct {
 | |
| 	// Number of top-level non-nil errors
 | |
| 	Count int
 | |
| 
 | |
| 	// Total number of errors including multiErrors
 | |
| 	Capacity int
 | |
| 
 | |
| 	// Index of the first non-nil error in the list. Value is meaningless if
 | |
| 	// Count is zero.
 | |
| 	FirstErrorIdx int
 | |
| 
 | |
| 	// Whether the list contains at least one multiError
 | |
| 	ContainsMultiError bool
 | |
| }
 | |
| 
 | |
| // Inspects the given slice of errors so that we can efficiently allocate
 | |
| // space for it.
 | |
| func inspect(errors []error) (res inspectResult) {
 | |
| 	first := true
 | |
| 	for i, err := range errors {
 | |
| 		if err == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		res.Count++
 | |
| 		if first {
 | |
| 			first = false
 | |
| 			res.FirstErrorIdx = i
 | |
| 		}
 | |
| 
 | |
| 		if merr, ok := err.(*multiError); ok {
 | |
| 			res.Capacity += len(merr.errors)
 | |
| 			res.ContainsMultiError = true
 | |
| 		} else {
 | |
| 			res.Capacity++
 | |
| 		}
 | |
| 	}
 | |
| 	return
 | |
| }
 | |
| 
 | |
| // fromSlice converts the given list of errors into a single error.
 | |
| func fromSlice(errors []error) error {
 | |
| 	res := inspect(errors)
 | |
| 	switch res.Count {
 | |
| 	case 0:
 | |
| 		return nil
 | |
| 	case 1:
 | |
| 		// only one non-nil entry
 | |
| 		return errors[res.FirstErrorIdx]
 | |
| 	case len(errors):
 | |
| 		if !res.ContainsMultiError {
 | |
| 			// already flat
 | |
| 			return &multiError{errors: errors}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	nonNilErrs := make([]error, 0, res.Capacity)
 | |
| 	for _, err := range errors[res.FirstErrorIdx:] {
 | |
| 		if err == nil {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if nested, ok := err.(*multiError); ok {
 | |
| 			nonNilErrs = append(nonNilErrs, nested.errors...)
 | |
| 		} else {
 | |
| 			nonNilErrs = append(nonNilErrs, err)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return &multiError{errors: nonNilErrs}
 | |
| }
 | |
| 
 | |
| // Combine combines the passed errors into a single error.
 | |
| //
 | |
| // If zero arguments were passed or if all items are nil, a nil error is
 | |
| // returned.
 | |
| //
 | |
| // 	Combine(nil, nil)  // == nil
 | |
| //
 | |
| // If only a single error was passed, it is returned as-is.
 | |
| //
 | |
| // 	Combine(err)  // == err
 | |
| //
 | |
| // Combine skips over nil arguments so this function may be used to combine
 | |
| // together errors from operations that fail independently of each other.
 | |
| //
 | |
| // 	multierr.Combine(
 | |
| // 		reader.Close(),
 | |
| // 		writer.Close(),
 | |
| // 		pipe.Close(),
 | |
| // 	)
 | |
| //
 | |
| // If any of the passed errors is a multierr error, it will be flattened along
 | |
| // with the other errors.
 | |
| //
 | |
| // 	multierr.Combine(multierr.Combine(err1, err2), err3)
 | |
| // 	// is the same as
 | |
| // 	multierr.Combine(err1, err2, err3)
 | |
| //
 | |
| // The returned error formats into a readable multi-line error message if
 | |
| // formatted with %+v.
 | |
| //
 | |
| // 	fmt.Sprintf("%+v", multierr.Combine(err1, err2))
 | |
| func Combine(errors ...error) error {
 | |
| 	return fromSlice(errors)
 | |
| }
 | |
| 
 | |
| // Append appends the given errors together. Either value may be nil.
 | |
| //
 | |
| // This function is a specialization of Combine for the common case where
 | |
| // there are only two errors.
 | |
| //
 | |
| // 	err = multierr.Append(reader.Close(), writer.Close())
 | |
| //
 | |
| // The following pattern may also be used to record failure of deferred
 | |
| // operations without losing information about the original error.
 | |
| //
 | |
| // 	func doSomething(..) (err error) {
 | |
| // 		f := acquireResource()
 | |
| // 		defer func() {
 | |
| // 			err = multierr.Append(err, f.Close())
 | |
| // 		}()
 | |
| func Append(left error, right error) error {
 | |
| 	switch {
 | |
| 	case left == nil:
 | |
| 		return right
 | |
| 	case right == nil:
 | |
| 		return left
 | |
| 	}
 | |
| 
 | |
| 	if _, ok := right.(*multiError); !ok {
 | |
| 		if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) {
 | |
| 			// Common case where the error on the left is constantly being
 | |
| 			// appended to.
 | |
| 			errs := append(l.errors, right)
 | |
| 			return &multiError{errors: errs}
 | |
| 		} else if !ok {
 | |
| 			// Both errors are single errors.
 | |
| 			return &multiError{errors: []error{left, right}}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Either right or both, left and right, are multiErrors. Rely on usual
 | |
| 	// expensive logic.
 | |
| 	errors := [2]error{left, right}
 | |
| 	return fromSlice(errors[0:])
 | |
| }
 |