github.com/jenkins-x/jx/v2@v2.1.155/pkg/errorutil/errors.go (about)

     1  package errorutil
     2  
     3  import (
     4  	"fmt"
     5  )
     6  
     7  // CombineErrors combines the non null errors into a single error or returns null
     8  func CombineErrors(errs ...error) error {
     9  	var answer []error
    10  	for _, err := range errs {
    11  		if err != nil {
    12  			answer = append(answer, err)
    13  		}
    14  	}
    15  	if len(answer) == 0 {
    16  		return nil
    17  	} else if len(answer) == 1 {
    18  		return answer[0]
    19  	}
    20  	return NewAggregate(answer)
    21  }
    22  
    23  // Copied from k8s.io/apimachinery/pkg/util/errors to make this package independent of apimachinery (HF)
    24  
    25  // MessageCountMap contains occurrence for each error message.
    26  type MessageCountMap map[string]int
    27  
    28  // Aggregate represents an object that contains multiple errors, but does not
    29  // necessarily have singular semantic meaning.
    30  type Aggregate interface {
    31  	error
    32  	Errors() []error
    33  }
    34  
    35  // NewAggregate converts a slice of errors into an Aggregate interface, which
    36  // is itself an implementation of the error interface.  If the slice is empty,
    37  // this returns nil.
    38  // It will check if any of the element of input error list is nil, to avoid
    39  // nil pointer panic when call Error().
    40  func NewAggregate(errlist []error) Aggregate {
    41  	if len(errlist) == 0 {
    42  		return nil
    43  	}
    44  	// In case of input error list contains nil
    45  	var errs []error
    46  	for _, e := range errlist {
    47  		if e != nil {
    48  			errs = append(errs, e)
    49  		}
    50  	}
    51  	if len(errs) == 0 {
    52  		return nil
    53  	}
    54  	return aggregate(errs)
    55  }
    56  
    57  // This helper implements the error and Errors interfaces.  Keeping it private
    58  // prevents people from making an aggregate of 0 errors, which is not
    59  // an error, but does satisfy the error interface.
    60  type aggregate []error
    61  
    62  // Error is part of the error interface.
    63  func (agg aggregate) Error() string {
    64  	if len(agg) == 0 {
    65  		// This should never happen, really.
    66  		return ""
    67  	}
    68  	if len(agg) == 1 {
    69  		return agg[0].Error()
    70  	}
    71  	result := fmt.Sprintf("[%s", agg[0].Error())
    72  	for i := 1; i < len(agg); i++ {
    73  		result += fmt.Sprintf(", %s", agg[i].Error())
    74  	}
    75  	result += "]"
    76  	return result
    77  }
    78  
    79  // Errors is part of the Aggregate interface.
    80  func (agg aggregate) Errors() []error {
    81  	return []error(agg)
    82  }
    83  
    84  // Matcher is used to match errors.  Returns true if the error matches.
    85  type Matcher func(error) bool
    86  
    87  // FilterOut removes all errors that match any of the matchers from the input
    88  // error.  If the input is a singular error, only that error is tested.  If the
    89  // input implements the Aggregate interface, the list of errors will be
    90  // processed recursively.
    91  //
    92  // This can be used, for example, to remove known-OK errors (such as io.EOF or
    93  // os.PathNotFound) from a list of errors.
    94  func FilterOut(err error, fns ...Matcher) error {
    95  	if err == nil {
    96  		return nil
    97  	}
    98  	if agg, ok := err.(Aggregate); ok {
    99  		return NewAggregate(filterErrors(agg.Errors(), fns...))
   100  	}
   101  	if !matchesError(err, fns...) {
   102  		return err
   103  	}
   104  	return nil
   105  }
   106  
   107  // matchesError returns true if any Matcher returns true
   108  func matchesError(err error, fns ...Matcher) bool {
   109  	for _, fn := range fns {
   110  		if fn(err) {
   111  			return true
   112  		}
   113  	}
   114  	return false
   115  }
   116  
   117  // filterErrors returns any errors (or nested errors, if the list contains
   118  // nested Errors) for which all fns return false. If no errors
   119  // remain a nil list is returned. The resulting silec will have all
   120  // nested slices flattened as a side effect.
   121  func filterErrors(list []error, fns ...Matcher) []error {
   122  	result := []error{}
   123  	for _, err := range list {
   124  		r := FilterOut(err, fns...)
   125  		if r != nil {
   126  			result = append(result, r)
   127  		}
   128  	}
   129  	return result
   130  }
   131  
   132  // Flatten takes an Aggregate, which may hold other Aggregates in arbitrary
   133  // nesting, and flattens them all into a single Aggregate, recursively.
   134  func Flatten(agg Aggregate) Aggregate {
   135  	result := []error{}
   136  	if agg == nil {
   137  		return nil
   138  	}
   139  	for _, err := range agg.Errors() {
   140  		if a, ok := err.(Aggregate); ok {
   141  			r := Flatten(a)
   142  			if r != nil {
   143  				result = append(result, r.Errors()...)
   144  			}
   145  		} else {
   146  			if err != nil {
   147  				result = append(result, err)
   148  			}
   149  		}
   150  	}
   151  	return NewAggregate(result)
   152  }
   153  
   154  // CreateAggregateFromMessageCountMap converts MessageCountMap Aggregate
   155  func CreateAggregateFromMessageCountMap(m MessageCountMap) Aggregate {
   156  	if m == nil {
   157  		return nil
   158  	}
   159  	result := make([]error, 0, len(m))
   160  	for errStr, count := range m {
   161  		var countStr string
   162  		if count > 1 {
   163  			countStr = fmt.Sprintf(" (repeated %v times)", count)
   164  		}
   165  		result = append(result, fmt.Errorf("%v%v", errStr, countStr))
   166  	}
   167  	return NewAggregate(result)
   168  }
   169  
   170  // Reduce will return err or, if err is an Aggregate and only has one item,
   171  // the first item in the aggregate.
   172  func Reduce(err error) error {
   173  	if agg, ok := err.(Aggregate); ok && err != nil {
   174  		switch len(agg.Errors()) {
   175  		case 1:
   176  			return agg.Errors()[0]
   177  		case 0:
   178  			return nil
   179  		}
   180  	}
   181  	return err
   182  }
   183  
   184  // AggregateGoroutines runs the provided functions in parallel, stuffing all
   185  // non-nil errors into the returned Aggregate.
   186  // Returns nil if all the functions complete successfully.
   187  func AggregateGoroutines(funcs ...func() error) Aggregate {
   188  	errChan := make(chan error, len(funcs))
   189  	for _, f := range funcs {
   190  		go func(f func() error) { errChan <- f() }(f)
   191  	}
   192  	errs := make([]error, 0)
   193  	for i := 0; i < cap(errChan); i++ {
   194  		if err := <-errChan; err != nil {
   195  			errs = append(errs, err)
   196  		}
   197  	}
   198  	return NewAggregate(errs)
   199  }