github.com/mongodb/grip@v0.0.0-20240213223901-f906268d82b9/errors_multi.go (about)

     1  /*
     2  Error Catcher
     3  
     4  The MutiCatcher type makes it possible to collect from a group of
     5  operations and then aggregate them as a single error.
     6  */
     7  package grip
     8  
     9  import (
    10  	"fmt"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  // TODO: make a new catcher package, leave constructors in this
    18  // package, use this Catcher interface, and write implementations that
    19  // don't translate errors into string internally.
    20  //
    21  
    22  // CheckFunction are functions which take no arguments and return an
    23  // error.
    24  type CheckFunction func() error
    25  
    26  // Catcher is an interface for an error collector for use when
    27  // implementing continue-on-error semantics in concurrent
    28  // operations. There are three different Catcher implementations
    29  // provided by this package that differ *only* in terms of the
    30  // string format returned by String() (and also the format of the
    31  // error returned by Resolve().)
    32  //
    33  // If you do not use github.com/pkg/errors to attach
    34  // errors, the implementations are usually functionally
    35  // equivalent. The Extended variant formats the errors using the "%+v"
    36  // (which returns a full stack trace with pkg/errors,) the Simple
    37  // variant uses %s (which includes all the wrapped context,) and the
    38  // basic catcher calls error.Error() (which should be equvalent to %s
    39  // for most error implementations.)
    40  type Catcher interface {
    41  	Add(error)
    42  	AddWhen(bool, error)
    43  	Extend([]error)
    44  	ExtendWhen(bool, []error)
    45  	Len() int
    46  	HasErrors() bool
    47  	String() string
    48  	Resolve() error
    49  	Errors() []error
    50  
    51  	New(string)
    52  	NewWhen(bool, string)
    53  	Errorf(string, ...interface{})
    54  	ErrorfWhen(bool, string, ...interface{})
    55  
    56  	Wrap(error, string)
    57  	Wrapf(error, string, ...interface{})
    58  
    59  	Check(CheckFunction)
    60  	CheckExtend([]CheckFunction)
    61  	CheckWhen(bool, CheckFunction)
    62  }
    63  
    64  // multiCatcher provides an interface to collect and coalesse error
    65  // messages within a function or other sequence of operations. Used to
    66  // implement a kind of "continue on error"-style operations. The
    67  // methods on MultiCatatcher are thread-safe.
    68  type baseCatcher struct {
    69  	errs  []error
    70  	mutex sync.RWMutex
    71  	fmt.Stringer
    72  }
    73  
    74  // NewCatcher returns a Catcher instance that you can use to capture
    75  // error messages and aggregate the errors. For consistency with
    76  // earlier versions NewCatcher is the same as NewExtendedCatcher()
    77  //
    78  // DEPRECATED: use one of the other catcher implementations. See the
    79  // documentation for the Catcher interface for most implementations.
    80  func NewCatcher() Catcher { return NewExtendedCatcher() }
    81  
    82  // NewBasicCatcher collects error messages and formats them using a
    83  // new-line separated string of the output of error.Error()
    84  func NewBasicCatcher() Catcher {
    85  	c := &baseCatcher{}
    86  	c.Stringer = &basicCatcher{c}
    87  	return c
    88  }
    89  
    90  // NewSimpleCatcher collects error messages and formats them using a
    91  // new-line separated string of the string format of the error message
    92  // (e.g. %s).
    93  func NewSimpleCatcher() Catcher {
    94  	c := &baseCatcher{}
    95  	c.Stringer = &simpleCatcher{c}
    96  	return c
    97  }
    98  
    99  // NewExtendedCatcher collects error messages and formats them using a
   100  // new-line separated string of the extended string format of the
   101  // error message (e.g. %+v).
   102  func NewExtendedCatcher() Catcher {
   103  	c := &baseCatcher{}
   104  	c.Stringer = &extendedCatcher{c}
   105  	return c
   106  }
   107  
   108  // Add takes an error object and, if it's non-nil, adds it to the
   109  // internal collection of errors.
   110  func (c *baseCatcher) Add(err error) {
   111  	if err == nil {
   112  		return
   113  	}
   114  
   115  	c.mutex.Lock()
   116  	defer c.mutex.Unlock()
   117  
   118  	c.errs = append(c.errs, err)
   119  }
   120  
   121  // Len returns the number of errors stored in the collector.
   122  func (c *baseCatcher) Len() int {
   123  	c.mutex.RLock()
   124  	defer c.mutex.RUnlock()
   125  
   126  	return len(c.errs)
   127  }
   128  
   129  // HasErrors returns true if the collector has ingested errors, and
   130  // false otherwise.
   131  func (c *baseCatcher) HasErrors() bool {
   132  	c.mutex.RLock()
   133  	defer c.mutex.RUnlock()
   134  
   135  	return len(c.errs) > 0
   136  }
   137  
   138  // Extend adds all non-nil errors, passed as arguments to the catcher.
   139  func (c *baseCatcher) Extend(errs []error) {
   140  	if len(errs) == 0 {
   141  		return
   142  	}
   143  
   144  	c.mutex.Lock()
   145  	defer c.mutex.Unlock()
   146  
   147  	for _, err := range errs {
   148  		if err == nil {
   149  			continue
   150  		}
   151  
   152  		c.errs = append(c.errs, err)
   153  	}
   154  }
   155  
   156  func (c *baseCatcher) Errorf(form string, args ...interface{}) {
   157  	if form == "" {
   158  		return
   159  	} else if len(args) == 0 {
   160  		c.New(form)
   161  		return
   162  	}
   163  	c.Add(errors.Errorf(form, args...))
   164  }
   165  
   166  func (c *baseCatcher) New(e string) {
   167  	if e == "" {
   168  		return
   169  	}
   170  	c.Add(errors.New(e))
   171  }
   172  
   173  func (c *baseCatcher) Wrap(err error, m string) { c.Add(errors.Wrap(err, m)) }
   174  
   175  func (c *baseCatcher) Wrapf(err error, f string, args ...interface{}) {
   176  	c.Add(errors.Wrapf(err, f, args...))
   177  }
   178  
   179  func (c *baseCatcher) AddWhen(cond bool, err error) {
   180  	if !cond {
   181  		return
   182  	}
   183  
   184  	c.Add(err)
   185  }
   186  
   187  func (c *baseCatcher) ExtendWhen(cond bool, errs []error) {
   188  	if !cond {
   189  		return
   190  	}
   191  
   192  	c.Extend(errs)
   193  }
   194  
   195  func (c *baseCatcher) ErrorfWhen(cond bool, form string, args ...interface{}) {
   196  	if !cond {
   197  		return
   198  	}
   199  
   200  	c.Errorf(form, args...)
   201  }
   202  
   203  func (c *baseCatcher) NewWhen(cond bool, e string) {
   204  	if !cond {
   205  		return
   206  	}
   207  
   208  	c.New(e)
   209  }
   210  
   211  func (c *baseCatcher) Check(fn CheckFunction) { c.Add(fn()) }
   212  
   213  func (c *baseCatcher) CheckWhen(cond bool, fn CheckFunction) {
   214  	if !cond {
   215  		return
   216  	}
   217  
   218  	c.Add(fn())
   219  }
   220  
   221  func (c *baseCatcher) CheckExtend(fns []CheckFunction) {
   222  	for _, fn := range fns {
   223  		c.Add(fn())
   224  	}
   225  }
   226  
   227  func (c *baseCatcher) Errors() []error {
   228  	c.mutex.RLock()
   229  	defer c.mutex.RUnlock()
   230  
   231  	out := make([]error, len(c.errs))
   232  
   233  	copy(out, c.errs)
   234  
   235  	return out
   236  }
   237  
   238  // Resolve returns a final error object for the Catcher. If there are
   239  // no errors, it returns nil, and returns an error object with the
   240  // string form of all error objects in the collector.
   241  func (c *baseCatcher) Resolve() error {
   242  	if !c.HasErrors() {
   243  		return nil
   244  	}
   245  
   246  	return errors.New(c.String())
   247  }
   248  
   249  ////////////////////////////////////////////////////////////////////////
   250  //
   251  // separate implementations of grip.Catcher with different string formatting options.
   252  
   253  type extendedCatcher struct{ *baseCatcher }
   254  
   255  func (c *extendedCatcher) String() string {
   256  	c.mutex.RLock()
   257  	defer c.mutex.RUnlock()
   258  
   259  	output := make([]string, len(c.errs))
   260  
   261  	for idx, err := range c.errs {
   262  		output[idx] = fmt.Sprintf("%+v", err)
   263  	}
   264  
   265  	return strings.Join(output, "\n")
   266  }
   267  
   268  type simpleCatcher struct{ *baseCatcher }
   269  
   270  func (c *simpleCatcher) String() string {
   271  	c.mutex.RLock()
   272  	defer c.mutex.RUnlock()
   273  
   274  	output := make([]string, len(c.errs))
   275  
   276  	for idx, err := range c.errs {
   277  		output[idx] = fmt.Sprintf("%s", err)
   278  	}
   279  
   280  	return strings.Join(output, "\n")
   281  }
   282  
   283  type basicCatcher struct{ *baseCatcher }
   284  
   285  func (c *basicCatcher) String() string {
   286  	c.mutex.RLock()
   287  	defer c.mutex.RUnlock()
   288  
   289  	output := make([]string, len(c.errs))
   290  
   291  	for idx, err := range c.errs {
   292  		output[idx] = err.Error()
   293  	}
   294  
   295  	return strings.Join(output, "\n")
   296  }
   297  
   298  ////////////////////////////////////////////////////////////////////////
   299  //
   300  // an implementation to annotate errors with timestamps
   301  
   302  type timeAnnotatingCatcher struct {
   303  	errs     []*timestampError
   304  	extended bool
   305  	mu       sync.RWMutex
   306  }
   307  
   308  // NewTimestampCatcher produces a Catcher instance that reports the
   309  // short form of all constituent errors and annotates those errors
   310  // with a timestamp to reflect when the error was collected.
   311  func NewTimestampCatcher() Catcher { return &timeAnnotatingCatcher{} }
   312  
   313  // NewExtendedTimestampCatcher adds long-form annotation to the
   314  // aggregated error message (e.g. including stacks, when possible.)
   315  func NewExtendedTimestampCatcher() Catcher { return &timeAnnotatingCatcher{extended: true} }
   316  
   317  func (c *timeAnnotatingCatcher) Add(err error) {
   318  	if err == nil {
   319  		return
   320  	}
   321  
   322  	c.mu.Lock()
   323  	defer c.mu.Unlock()
   324  
   325  	tserr := newTimeStampError(err)
   326  
   327  	if tserr == nil {
   328  		return
   329  	}
   330  
   331  	c.errs = append(c.errs, tserr.setExtended(c.extended))
   332  }
   333  
   334  func (c *timeAnnotatingCatcher) Extend(errs []error) {
   335  	if len(errs) == 0 {
   336  		return
   337  	}
   338  
   339  	c.mu.Lock()
   340  	defer c.mu.Unlock()
   341  
   342  	for _, err := range errs {
   343  		if err == nil {
   344  			continue
   345  		}
   346  
   347  		c.errs = append(c.errs, newTimeStampError(err).setExtended(c.extended))
   348  	}
   349  }
   350  
   351  func (c *timeAnnotatingCatcher) AddWhen(cond bool, err error) {
   352  	if !cond {
   353  		return
   354  	}
   355  
   356  	c.Add(err)
   357  }
   358  
   359  func (c *timeAnnotatingCatcher) ExtendWhen(cond bool, errs []error) {
   360  	if !cond {
   361  		return
   362  	}
   363  
   364  	c.Extend(errs)
   365  }
   366  
   367  func (c *timeAnnotatingCatcher) New(e string) {
   368  	if e == "" {
   369  		return
   370  	}
   371  
   372  	c.Add(errors.New(e))
   373  }
   374  
   375  func (c *timeAnnotatingCatcher) NewWhen(cond bool, e string) {
   376  	if !cond {
   377  		return
   378  	}
   379  
   380  	c.New(e)
   381  }
   382  
   383  func (c *timeAnnotatingCatcher) Errorf(f string, args ...interface{}) {
   384  	if f == "" {
   385  		return
   386  	} else if len(args) == 0 {
   387  		c.New(f)
   388  		return
   389  	}
   390  
   391  	c.Add(errors.Errorf(f, args...))
   392  }
   393  
   394  func (c *timeAnnotatingCatcher) ErrorfWhen(cond bool, f string, args ...interface{}) {
   395  	if !cond {
   396  		return
   397  	}
   398  
   399  	c.Errorf(f, args...)
   400  }
   401  
   402  func (c *timeAnnotatingCatcher) Wrap(err error, m string) {
   403  	c.Add(WrapErrorTimeMessage(err, m))
   404  }
   405  
   406  func (c *timeAnnotatingCatcher) Wrapf(err error, f string, args ...interface{}) {
   407  	c.Add(WrapErrorTimeMessagef(err, f, args...))
   408  }
   409  
   410  func (c *timeAnnotatingCatcher) Check(fn CheckFunction) {
   411  	c.Add(fn())
   412  }
   413  
   414  func (c *timeAnnotatingCatcher) CheckWhen(cond bool, fn CheckFunction) {
   415  	if !cond {
   416  		return
   417  	}
   418  
   419  	c.Add(fn())
   420  }
   421  
   422  func (c *timeAnnotatingCatcher) CheckExtend(fns []CheckFunction) {
   423  	for _, fn := range fns {
   424  		c.Add(fn())
   425  	}
   426  }
   427  
   428  func (c *timeAnnotatingCatcher) Len() int {
   429  	c.mu.RLock()
   430  	defer c.mu.RUnlock()
   431  
   432  	return len(c.errs)
   433  }
   434  
   435  func (c *timeAnnotatingCatcher) HasErrors() bool {
   436  	c.mu.RLock()
   437  	defer c.mu.RUnlock()
   438  
   439  	return len(c.errs) > 0
   440  }
   441  
   442  func (c *timeAnnotatingCatcher) Errors() []error {
   443  	c.mu.RLock()
   444  	defer c.mu.RUnlock()
   445  
   446  	out := make([]error, len(c.errs))
   447  	for idx, err := range c.errs {
   448  		out[idx] = err
   449  	}
   450  
   451  	return out
   452  }
   453  
   454  func (c *timeAnnotatingCatcher) String() string {
   455  	c.mu.RLock()
   456  	defer c.mu.RUnlock()
   457  
   458  	output := make([]string, len(c.errs))
   459  
   460  	for idx, err := range c.errs {
   461  		if err.extended {
   462  			output[idx] = err.String()
   463  		} else {
   464  			output[idx] = err.String()
   465  		}
   466  	}
   467  
   468  	return strings.Join(output, "\n")
   469  }
   470  
   471  func (c *timeAnnotatingCatcher) Resolve() error {
   472  	if !c.HasErrors() {
   473  		return nil
   474  	}
   475  
   476  	return errors.New(c.String())
   477  }