Path: blob/main/vendor/go.uber.org/multierr/error.go
2872 views
// Copyright (c) 2017-2023 Uber Technologies, Inc.1//2// Permission is hereby granted, free of charge, to any person obtaining a copy3// of this software and associated documentation files (the "Software"), to deal4// in the Software without restriction, including without limitation the rights5// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell6// copies of the Software, and to permit persons to whom the Software is7// furnished to do so, subject to the following conditions:8//9// The above copyright notice and this permission notice shall be included in10// all copies or substantial portions of the Software.11//12// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR13// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,14// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE15// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER16// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,17// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN18// THE SOFTWARE.1920// Package multierr allows combining one or more errors together.21//22// # Overview23//24// Errors can be combined with the use of the Combine function.25//26// multierr.Combine(27// reader.Close(),28// writer.Close(),29// conn.Close(),30// )31//32// If only two errors are being combined, the Append function may be used33// instead.34//35// err = multierr.Append(reader.Close(), writer.Close())36//37// The underlying list of errors for a returned error object may be retrieved38// with the Errors function.39//40// errors := multierr.Errors(err)41// if len(errors) > 0 {42// fmt.Println("The following errors occurred:", errors)43// }44//45// # Appending from a loop46//47// You sometimes need to append into an error from a loop.48//49// var err error50// for _, item := range items {51// err = multierr.Append(err, process(item))52// }53//54// Cases like this may require knowledge of whether an individual instance55// failed. This usually requires introduction of a new variable.56//57// var err error58// for _, item := range items {59// if perr := process(item); perr != nil {60// log.Warn("skipping item", item)61// err = multierr.Append(err, perr)62// }63// }64//65// multierr includes AppendInto to simplify cases like this.66//67// var err error68// for _, item := range items {69// if multierr.AppendInto(&err, process(item)) {70// log.Warn("skipping item", item)71// }72// }73//74// This will append the error into the err variable, and return true if that75// individual error was non-nil.76//77// See [AppendInto] for more information.78//79// # Deferred Functions80//81// Go makes it possible to modify the return value of a function in a defer82// block if the function was using named returns. This makes it possible to83// record resource cleanup failures from deferred blocks.84//85// func sendRequest(req Request) (err error) {86// conn, err := openConnection()87// if err != nil {88// return err89// }90// defer func() {91// err = multierr.Append(err, conn.Close())92// }()93// // ...94// }95//96// multierr provides the Invoker type and AppendInvoke function to make cases97// like the above simpler and obviate the need for a closure. The following is98// roughly equivalent to the example above.99//100// func sendRequest(req Request) (err error) {101// conn, err := openConnection()102// if err != nil {103// return err104// }105// defer multierr.AppendInvoke(&err, multierr.Close(conn))106// // ...107// }108//109// See [AppendInvoke] and [Invoker] for more information.110//111// NOTE: If you're modifying an error from inside a defer, you MUST use a named112// return value for that function.113//114// # Advanced Usage115//116// Errors returned by Combine and Append MAY implement the following117// interface.118//119// type errorGroup interface {120// // Returns a slice containing the underlying list of errors.121// //122// // This slice MUST NOT be modified by the caller.123// Errors() []error124// }125//126// Note that if you need access to list of errors behind a multierr error, you127// should prefer using the Errors function. That said, if you need cheap128// read-only access to the underlying errors slice, you can attempt to cast129// the error to this interface. You MUST handle the failure case gracefully130// because errors returned by Combine and Append are not guaranteed to131// implement this interface.132//133// var errors []error134// group, ok := err.(errorGroup)135// if ok {136// errors = group.Errors()137// } else {138// errors = []error{err}139// }140package multierr // import "go.uber.org/multierr"141142import (143"bytes"144"errors"145"fmt"146"io"147"strings"148"sync"149"sync/atomic"150)151152var (153// Separator for single-line error messages.154_singlelineSeparator = []byte("; ")155156// Prefix for multi-line messages157_multilinePrefix = []byte("the following errors occurred:")158159// Prefix for the first and following lines of an item in a list of160// multi-line error messages.161//162// For example, if a single item is:163//164// foo165// bar166//167// It will become,168//169// - foo170// bar171_multilineSeparator = []byte("\n - ")172_multilineIndent = []byte(" ")173)174175// _bufferPool is a pool of bytes.Buffers.176var _bufferPool = sync.Pool{177New: func() interface{} {178return &bytes.Buffer{}179},180}181182type errorGroup interface {183Errors() []error184}185186// Errors returns a slice containing zero or more errors that the supplied187// error is composed of. If the error is nil, a nil slice is returned.188//189// err := multierr.Append(r.Close(), w.Close())190// errors := multierr.Errors(err)191//192// If the error is not composed of other errors, the returned slice contains193// just the error that was passed in.194//195// Callers of this function are free to modify the returned slice.196func Errors(err error) []error {197return extractErrors(err)198}199200// multiError is an error that holds one or more errors.201//202// An instance of this is guaranteed to be non-empty and flattened. That is,203// none of the errors inside multiError are other multiErrors.204//205// multiError formats to a semi-colon delimited list of error messages with206// %v and with a more readable multi-line format with %+v.207type multiError struct {208copyNeeded atomic.Bool209errors []error210}211212// Errors returns the list of underlying errors.213//214// This slice MUST NOT be modified.215func (merr *multiError) Errors() []error {216if merr == nil {217return nil218}219return merr.errors220}221222func (merr *multiError) Error() string {223if merr == nil {224return ""225}226227buff := _bufferPool.Get().(*bytes.Buffer)228buff.Reset()229230merr.writeSingleline(buff)231232result := buff.String()233_bufferPool.Put(buff)234return result235}236237// Every compares every error in the given err against the given target error238// using [errors.Is], and returns true only if every comparison returned true.239func Every(err error, target error) bool {240for _, e := range extractErrors(err) {241if !errors.Is(e, target) {242return false243}244}245return true246}247248func (merr *multiError) Format(f fmt.State, c rune) {249if c == 'v' && f.Flag('+') {250merr.writeMultiline(f)251} else {252merr.writeSingleline(f)253}254}255256func (merr *multiError) writeSingleline(w io.Writer) {257first := true258for _, item := range merr.errors {259if first {260first = false261} else {262w.Write(_singlelineSeparator)263}264io.WriteString(w, item.Error())265}266}267268func (merr *multiError) writeMultiline(w io.Writer) {269w.Write(_multilinePrefix)270for _, item := range merr.errors {271w.Write(_multilineSeparator)272writePrefixLine(w, _multilineIndent, fmt.Sprintf("%+v", item))273}274}275276// Writes s to the writer with the given prefix added before each line after277// the first.278func writePrefixLine(w io.Writer, prefix []byte, s string) {279first := true280for len(s) > 0 {281if first {282first = false283} else {284w.Write(prefix)285}286287idx := strings.IndexByte(s, '\n')288if idx < 0 {289idx = len(s) - 1290}291292io.WriteString(w, s[:idx+1])293s = s[idx+1:]294}295}296297type inspectResult struct {298// Number of top-level non-nil errors299Count int300301// Total number of errors including multiErrors302Capacity int303304// Index of the first non-nil error in the list. Value is meaningless if305// Count is zero.306FirstErrorIdx int307308// Whether the list contains at least one multiError309ContainsMultiError bool310}311312// Inspects the given slice of errors so that we can efficiently allocate313// space for it.314func inspect(errors []error) (res inspectResult) {315first := true316for i, err := range errors {317if err == nil {318continue319}320321res.Count++322if first {323first = false324res.FirstErrorIdx = i325}326327if merr, ok := err.(*multiError); ok {328res.Capacity += len(merr.errors)329res.ContainsMultiError = true330} else {331res.Capacity++332}333}334return335}336337// fromSlice converts the given list of errors into a single error.338func fromSlice(errors []error) error {339// Don't pay to inspect small slices.340switch len(errors) {341case 0:342return nil343case 1:344return errors[0]345}346347res := inspect(errors)348switch res.Count {349case 0:350return nil351case 1:352// only one non-nil entry353return errors[res.FirstErrorIdx]354case len(errors):355if !res.ContainsMultiError {356// Error list is flat. Make a copy of it357// Otherwise "errors" escapes to the heap358// unconditionally for all other cases.359// This lets us optimize for the "no errors" case.360out := append(([]error)(nil), errors...)361return &multiError{errors: out}362}363}364365nonNilErrs := make([]error, 0, res.Capacity)366for _, err := range errors[res.FirstErrorIdx:] {367if err == nil {368continue369}370371if nested, ok := err.(*multiError); ok {372nonNilErrs = append(nonNilErrs, nested.errors...)373} else {374nonNilErrs = append(nonNilErrs, err)375}376}377378return &multiError{errors: nonNilErrs}379}380381// Combine combines the passed errors into a single error.382//383// If zero arguments were passed or if all items are nil, a nil error is384// returned.385//386// Combine(nil, nil) // == nil387//388// If only a single error was passed, it is returned as-is.389//390// Combine(err) // == err391//392// Combine skips over nil arguments so this function may be used to combine393// together errors from operations that fail independently of each other.394//395// multierr.Combine(396// reader.Close(),397// writer.Close(),398// pipe.Close(),399// )400//401// If any of the passed errors is a multierr error, it will be flattened along402// with the other errors.403//404// multierr.Combine(multierr.Combine(err1, err2), err3)405// // is the same as406// multierr.Combine(err1, err2, err3)407//408// The returned error formats into a readable multi-line error message if409// formatted with %+v.410//411// fmt.Sprintf("%+v", multierr.Combine(err1, err2))412func Combine(errors ...error) error {413return fromSlice(errors)414}415416// Append appends the given errors together. Either value may be nil.417//418// This function is a specialization of Combine for the common case where419// there are only two errors.420//421// err = multierr.Append(reader.Close(), writer.Close())422//423// The following pattern may also be used to record failure of deferred424// operations without losing information about the original error.425//426// func doSomething(..) (err error) {427// f := acquireResource()428// defer func() {429// err = multierr.Append(err, f.Close())430// }()431//432// Note that the variable MUST be a named return to append an error to it from433// the defer statement. See also [AppendInvoke].434func Append(left error, right error) error {435switch {436case left == nil:437return right438case right == nil:439return left440}441442if _, ok := right.(*multiError); !ok {443if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) {444// Common case where the error on the left is constantly being445// appended to.446errs := append(l.errors, right)447return &multiError{errors: errs}448} else if !ok {449// Both errors are single errors.450return &multiError{errors: []error{left, right}}451}452}453454// Either right or both, left and right, are multiErrors. Rely on usual455// expensive logic.456errors := [2]error{left, right}457return fromSlice(errors[0:])458}459460// AppendInto appends an error into the destination of an error pointer and461// returns whether the error being appended was non-nil.462//463// var err error464// multierr.AppendInto(&err, r.Close())465// multierr.AppendInto(&err, w.Close())466//467// The above is equivalent to,468//469// err := multierr.Append(r.Close(), w.Close())470//471// As AppendInto reports whether the provided error was non-nil, it may be472// used to build a multierr error in a loop more ergonomically. For example:473//474// var err error475// for line := range lines {476// var item Item477// if multierr.AppendInto(&err, parse(line, &item)) {478// continue479// }480// items = append(items, item)481// }482//483// Compare this with a version that relies solely on Append:484//485// var err error486// for line := range lines {487// var item Item488// if parseErr := parse(line, &item); parseErr != nil {489// err = multierr.Append(err, parseErr)490// continue491// }492// items = append(items, item)493// }494func AppendInto(into *error, err error) (errored bool) {495if into == nil {496// We panic if 'into' is nil. This is not documented above497// because suggesting that the pointer must be non-nil may498// confuse users into thinking that the error that it points499// to must be non-nil.500panic("misuse of multierr.AppendInto: into pointer must not be nil")501}502503if err == nil {504return false505}506*into = Append(*into, err)507return true508}509510// Invoker is an operation that may fail with an error. Use it with511// AppendInvoke to append the result of calling the function into an error.512// This allows you to conveniently defer capture of failing operations.513//514// See also, [Close] and [Invoke].515type Invoker interface {516Invoke() error517}518519// Invoke wraps a function which may fail with an error to match the Invoker520// interface. Use it to supply functions matching this signature to521// AppendInvoke.522//523// For example,524//525// func processReader(r io.Reader) (err error) {526// scanner := bufio.NewScanner(r)527// defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err))528// for scanner.Scan() {529// // ...530// }531// // ...532// }533//534// In this example, the following line will construct the Invoker right away,535// but defer the invocation of scanner.Err() until the function returns.536//537// defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err))538//539// Note that the error you're appending to from the defer statement MUST be a540// named return.541type Invoke func() error542543// Invoke calls the supplied function and returns its result.544func (i Invoke) Invoke() error { return i() }545546// Close builds an Invoker that closes the provided io.Closer. Use it with547// AppendInvoke to close io.Closers and append their results into an error.548//549// For example,550//551// func processFile(path string) (err error) {552// f, err := os.Open(path)553// if err != nil {554// return err555// }556// defer multierr.AppendInvoke(&err, multierr.Close(f))557// return processReader(f)558// }559//560// In this example, multierr.Close will construct the Invoker right away, but561// defer the invocation of f.Close until the function returns.562//563// defer multierr.AppendInvoke(&err, multierr.Close(f))564//565// Note that the error you're appending to from the defer statement MUST be a566// named return.567func Close(closer io.Closer) Invoker {568return Invoke(closer.Close)569}570571// AppendInvoke appends the result of calling the given Invoker into the572// provided error pointer. Use it with named returns to safely defer573// invocation of fallible operations until a function returns, and capture the574// resulting errors.575//576// func doSomething(...) (err error) {577// // ...578// f, err := openFile(..)579// if err != nil {580// return err581// }582//583// // multierr will call f.Close() when this function returns and584// // if the operation fails, its append its error into the585// // returned error.586// defer multierr.AppendInvoke(&err, multierr.Close(f))587//588// scanner := bufio.NewScanner(f)589// // Similarly, this scheduled scanner.Err to be called and590// // inspected when the function returns and append its error591// // into the returned error.592// defer multierr.AppendInvoke(&err, multierr.Invoke(scanner.Err))593//594// // ...595// }596//597// NOTE: If used with a defer, the error variable MUST be a named return.598//599// Without defer, AppendInvoke behaves exactly like AppendInto.600//601// err := // ...602// multierr.AppendInvoke(&err, mutltierr.Invoke(foo))603//604// // ...is roughly equivalent to...605//606// err := // ...607// multierr.AppendInto(&err, foo())608//609// The advantage of the indirection introduced by Invoker is to make it easy610// to defer the invocation of a function. Without this indirection, the611// invoked function will be evaluated at the time of the defer block rather612// than when the function returns.613//614// // BAD: This is likely not what the caller intended. This will evaluate615// // foo() right away and append its result into the error when the616// // function returns.617// defer multierr.AppendInto(&err, foo())618//619// // GOOD: This will defer invocation of foo unutil the function returns.620// defer multierr.AppendInvoke(&err, multierr.Invoke(foo))621//622// multierr provides a few Invoker implementations out of the box for623// convenience. See [Invoker] for more information.624func AppendInvoke(into *error, invoker Invoker) {625AppendInto(into, invoker.Invoke())626}627628// AppendFunc is a shorthand for [AppendInvoke].629// It allows using function or method value directly630// without having to wrap it into an [Invoker] interface.631//632// func doSomething(...) (err error) {633// w, err := startWorker(...)634// if err != nil {635// return err636// }637//638// // multierr will call w.Stop() when this function returns and639// // if the operation fails, it appends its error into the640// // returned error.641// defer multierr.AppendFunc(&err, w.Stop)642// }643func AppendFunc(into *error, fn func() error) {644AppendInvoke(into, Invoke(fn))645}646647648