go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/errors/multierror.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package errors
    16  
    17  import (
    18  	"fmt"
    19  )
    20  
    21  // MultiError is a simple `error` implementation which represents multiple
    22  // `error` objects in one.
    23  type MultiError []error
    24  
    25  // MaybeAdd will add `err` to `m` if `err` is not nil.
    26  func (m *MultiError) MaybeAdd(err error) {
    27  	if err == nil {
    28  		return
    29  	}
    30  	*m = append(*m, err)
    31  }
    32  
    33  func (m MultiError) Error() string {
    34  	n, e := m.Summary()
    35  	switch n {
    36  	case 0:
    37  		return "(0 errors)"
    38  	case 1:
    39  		return e.Error()
    40  	case 2:
    41  		return e.Error() + " (and 1 other error)"
    42  	}
    43  	return fmt.Sprintf("%s (and %d other errors)", e, n-1)
    44  }
    45  
    46  // AsError returns an `error` interface for this MultiError only if it has >0
    47  // length.
    48  func (m MultiError) AsError() error {
    49  	if len(m) == 0 {
    50  		return nil
    51  	}
    52  	return m
    53  }
    54  
    55  // Summary gets the total count of non-nil errors and returns the first one.
    56  func (m MultiError) Summary() (n int, first error) {
    57  	for _, e := range m {
    58  		if e != nil {
    59  			if n == 0 {
    60  				first = e
    61  			}
    62  			n++
    63  		}
    64  	}
    65  	return
    66  }
    67  
    68  // First returns the first non-nil error.
    69  func (m MultiError) First() error {
    70  	for _, e := range m {
    71  		if e != nil {
    72  			return e
    73  		}
    74  	}
    75  	return nil
    76  }
    77  
    78  func (m MultiError) stackContext() stackContext {
    79  	n, _ := m.Summary()
    80  
    81  	return stackContext{
    82  		internalReason: fmt.Sprintf(
    83  			"MultiError %d/%d: following first non-nil error.", n, len(m)),
    84  	}
    85  }
    86  
    87  // NewMultiError create new multi error from given errors.
    88  //
    89  // Can be used to workaround 'go vet' confusion "composite literal uses unkeyed
    90  // fields" or if you do not want to remember that MultiError is in fact []error.
    91  func NewMultiError(errors ...error) MultiError {
    92  	return errors
    93  }
    94  
    95  // SingleError provides a simple way to uwrap a MultiError if you know that it
    96  // could only ever contain one element.
    97  //
    98  // If err is a MultiError, return its first element. Otherwise, return err.
    99  func SingleError(err error) error {
   100  	if me, ok := err.(MultiError); ok {
   101  		if len(me) == 0 {
   102  			return nil
   103  		}
   104  		return me[0]
   105  	}
   106  	return err
   107  }
   108  
   109  // Flatten collapses a multi-dimensional MultiError space into a flat
   110  // MultiError, removing "nil" errors.
   111  //
   112  // If err is not an errors.MultiError, will return err directly.
   113  //
   114  // As a special case, if merr contains no non-nil errors, nil will be returned.
   115  func Flatten(err error) error {
   116  	var ret MultiError
   117  	flattenRec(&ret, err)
   118  	if len(ret) == 0 {
   119  		return nil
   120  	}
   121  	return ret
   122  }
   123  
   124  func flattenRec(ret *MultiError, err error) {
   125  	switch et := err.(type) {
   126  	case nil:
   127  	case MultiError:
   128  		for _, e := range et {
   129  			flattenRec(ret, e)
   130  		}
   131  	default:
   132  		*ret = append(*ret, et)
   133  	}
   134  }
   135  
   136  // extract removes unnecessary singletons and empty multierrors from a MultiError.
   137  //
   138  // It is not recursive.
   139  func extract(e error) error {
   140  	switch et := e.(type) {
   141  	case nil:
   142  		return nil
   143  	case MultiError:
   144  		switch len(et) {
   145  		case 0:
   146  			return nil
   147  		case 1:
   148  			return et[0]
   149  		}
   150  	}
   151  	return e
   152  }
   153  
   154  // Append takes a list of errors, whether they are nil, MultiErrors, or regular errors, and combines them into a single error.
   155  //
   156  // The resulting error is not a multierror unless it needs to be.
   157  //
   158  // Sample usage shown below:
   159  //
   160  //	err := DoSomething()
   161  //	err = errors.Append(err, DoSomethingElse())
   162  //	err = errors.Append(err, DoAThirdThing())
   163  //
   164  //	if err != nil {
   165  //	  // log an error or something, I don't know
   166  //	}
   167  //	// proceed as normal
   168  func Append(errs ...error) error {
   169  	return extract(Flatten(MultiError(errs)))
   170  }