lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/xerr/xerr.go (about)

     1  // Copyright (C) 2015-2020  Nexedi SA and Contributors.
     2  //                          Kirill Smelkov <kirr@nexedi.com>
     3  //
     4  // This program is free software: you can Use, Study, Modify and Redistribute
     5  // it under the terms of the GNU General Public License version 3, or (at your
     6  // option) any later version, as published by the Free Software Foundation.
     7  //
     8  // You can also Link and Combine this program with other software covered by
     9  // the terms of any of the Free Software licenses or any of the Open Source
    10  // Initiative approved licenses and Convey the resulting work. Corresponding
    11  // source of such a combination shall include the source code for all other
    12  // software used.
    13  //
    14  // This program is distributed WITHOUT ANY WARRANTY; without even the implied
    15  // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    16  //
    17  // See COPYING file for full licensing terms.
    18  // See https://www.nexedi.com/licensing for rationale and options.
    19  
    20  // Package xerr provides addons for error-handling.
    21  //
    22  // # Error context
    23  //
    24  // Context and Contextf are handy to concisely add context to returned error,
    25  // for example:
    26  //
    27  //	func myfunc(arg1, arg2 string) (..., err error) {
    28  //		defer xerr.Contextf(&err, "doing something (%s, %s)", arg1, arg2)
    29  //		...
    30  //
    31  // which will, if returned error is !nil, wrap it with the following prefix:
    32  //
    33  //	"doing something (%s, %s):" % (arg1, arg2)
    34  //
    35  // The original unwrapped error will be still accessible as the cause of
    36  // returned error.  Please see package github.com/pkg/errors for details on
    37  // this topic.
    38  //
    39  // # Error vector
    40  //
    41  // Sometimes there are several operations performed and we want to collect
    42  // errors from them all. For this Errorv could be used which is vector of
    43  // errors and at the same time an error itself. After collecting it is possible
    44  // to extract resulting error from the vector in canonical form with
    45  // Errorv.Err. Errorv also provides handy ways to append errors to the vector
    46  // - see Errorv.Append* for details.
    47  //
    48  // For convenience Merge could be used to concisely construct error vector
    49  // from !nil errors and extract its canonical form in one line, for example:
    50  //
    51  //	err1 := op1(...)
    52  //	err2 := op2(...)
    53  //	err3 := op3(...)
    54  //
    55  //	err := xerr.Merge(err1, err2, err3)
    56  //	return err
    57  //
    58  // There is also First counterpart to Merge, which returns only first !nil
    59  // error.
    60  //
    61  // Since Errorv is actually a slice it cannot be generally compared - for example
    62  // comparing 2 error interfaces that both have dynamic type Errorv will panic
    63  // at runtime. However it is possible to compare Errorv to other error types,
    64  // because interfaces with different dynamic types are always not equal. For
    65  // example the following works:
    66  //
    67  //	var err error = Errorv{...} // received as result from a function
    68  //	if err == io.EOF {
    69  //		...
    70  package xerr
    71  
    72  import (
    73  	"fmt"
    74  
    75  	"github.com/pkg/errors"
    76  )
    77  
    78  // Errorv is error vector merging multiple errors (e.g. after collecting them from several parallel workers).
    79  type Errorv []error
    80  
    81  // Error returns string representation of error vector.
    82  //
    83  //   - ""			if len(errv)==0
    84  //   - errv[0].Error()	if len(errv)==1
    85  //   - "<n> errors:\n" + string representation of every error on separate line, otherwise.
    86  func (errv Errorv) Error() string {
    87  	switch len(errv) {
    88  	case 0:
    89  		return ""
    90  	case 1:
    91  		return errv[0].Error()
    92  	}
    93  
    94  	msg := fmt.Sprintf("%d errors:\n", len(errv))
    95  	for _, e := range errv {
    96  		msg += fmt.Sprintf("\t- %s\n", e)
    97  	}
    98  	return msg
    99  }
   100  
   101  // Append appends err to error vector.
   102  func (errv *Errorv) Append(err error) {
   103  	*errv = append(*errv, err)
   104  }
   105  
   106  // Appendif appends err to error vector if err != nil.
   107  func (errv *Errorv) Appendif(err error) {
   108  	if err == nil {
   109  		return
   110  	}
   111  	errv.Append(err)
   112  }
   113  
   114  // Appendf appends formatted error string.
   115  func (errv *Errorv) Appendf(format string, a ...interface{}) {
   116  	errv.Append(fmt.Errorf(format, a...))
   117  }
   118  
   119  // Err returns error in canonical form accumulated in error vector.
   120  //
   121  //   - nil      if len(errv)==0
   122  //   - errv[0]  if len(errv)==1		// XXX is this good idea?
   123  //   - errv     otherwise
   124  func (errv Errorv) Err() error {
   125  	switch len(errv) {
   126  	case 0:
   127  		return nil
   128  	case 1:
   129  		return errv[0]
   130  	default:
   131  		return errv
   132  	}
   133  }
   134  
   135  // Merge merges non-nil errors into one error.
   136  //
   137  // it returns:
   138  //
   139  //   - nil                         if all errors are nil
   140  //   - single error                if there is only one non-nil error
   141  //   - Errorv with non-nil errors  if there is more than one non-nil error
   142  func Merge(errv ...error) error {
   143  	ev := Errorv{}
   144  	for _, err := range errv {
   145  		ev.Appendif(err)
   146  	}
   147  	return ev.Err()
   148  }
   149  
   150  // First returns first non-nil error, or nil if there is no errors.
   151  func First(errv ...error) error {
   152  	for _, err := range errv {
   153  		if err != nil {
   154  			return err
   155  		}
   156  	}
   157  	return nil
   158  }
   159  
   160  // ----------------------------------------
   161  
   162  // Context provides error context to be automatically added on error return.
   163  //
   164  // Intended to be used under defer like this:
   165  //
   166  //	func myfunc(...) (..., err error) {
   167  //		defer xerr.Context(&err, "error context")
   168  //		...
   169  //
   170  // It is also possible to use Context directly to add context to an error if it
   171  // is non-nil:
   172  //
   173  //	..., myerr := f()
   174  //	xerr.Context(&myerr, "while doing something")
   175  //
   176  // which is equivalent to
   177  //
   178  //	import "github.com/pkg/errors"
   179  //
   180  //	..., myerr := f()
   181  //	if myerr != nil {
   182  //		myerr = errors.WithMessage(myerr, "while doing something")
   183  //	}
   184  func Context(errp *error, context string) {
   185  	if *errp == nil {
   186  		return
   187  	}
   188  	*errp = errors.WithMessage(*errp, context)
   189  }
   190  
   191  // Contextf provides formatted error context to be automatically added on error return.
   192  //
   193  // Contextf is formatted analog of Context. Please see Context for details on how to use.
   194  func Contextf(errp *error, format string, argv ...interface{}) {
   195  	if *errp == nil {
   196  		return
   197  	}
   198  
   199  	*errp = errors.WithMessage(*errp, fmt.Sprintf(format, argv...))
   200  }