github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/utils/retry/multi_error.go (about)

     1  package retry
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strings"
     7  )
     8  
     9  // NewSizedError returns an multiple error which holds at most size errors.
    10  // The SizedError implementation is copied from github.com/jxskiss/errors
    11  // to remove dependency of the package and for better compatibility for
    12  // future go versions.
    13  func NewSizedError(size int) *SizedError {
    14  	return &SizedError{
    15  		errs: make([]error, size),
    16  		size: size,
    17  	}
    18  }
    19  
    20  type SizedError struct {
    21  	errs  []error
    22  	size  int
    23  	count int
    24  }
    25  
    26  func (E *SizedError) Append(errs ...error) {
    27  	for _, err := range errs {
    28  		if err != nil {
    29  			E.errs[E.count%E.size] = err
    30  			E.count++
    31  		}
    32  	}
    33  }
    34  
    35  func (E *SizedError) Error() string {
    36  	if E == nil || E.count == 0 {
    37  		return "<nil>"
    38  	}
    39  	var buf bytes.Buffer
    40  	var first = true
    41  	for _, err := range E.Errors() {
    42  		if first {
    43  			first = false
    44  		} else {
    45  			buf.Write(_singlelineSeparator)
    46  		}
    47  		buf.WriteString(err.Error())
    48  	}
    49  	return buf.String()
    50  }
    51  
    52  func (E *SizedError) ErrOrNil() error {
    53  	if E == nil || E.count == 0 {
    54  		return nil
    55  	}
    56  	return E
    57  }
    58  
    59  // Errors returns the errors as a slice in reversed order, if the underlying
    60  // errors are more than size, only size errors will be returned, plus an
    61  // additional error indicates the omitted error count.
    62  func (E *SizedError) Errors() (errors []error) {
    63  	if E.count == 0 {
    64  		return nil
    65  	}
    66  	if E.count <= E.size {
    67  		errors = make([]error, 0, E.count)
    68  		for i := E.count - 1; i >= 0; i-- {
    69  			errors = append(errors, E.errs[i])
    70  		}
    71  		return errors
    72  	}
    73  	errors = make([]error, 0, E.count+1)
    74  	for i := E.count%E.size - 1; i >= 0; i-- {
    75  		errors = append(errors, E.errs[i])
    76  	}
    77  	for i := E.size - 1; i >= E.count%E.size; i-- {
    78  		errors = append(errors, E.errs[i])
    79  	}
    80  	errors = append(errors, fmt.Errorf("and %d more errors omitted", E.count-E.size))
    81  	return errors
    82  }
    83  
    84  func (E *SizedError) Format(f fmt.State, c rune) {
    85  	if c == 'v' && f.Flag('+') {
    86  		f.Write(formatMultiLine(E.Errors()))
    87  	} else {
    88  		f.Write(formatSingleLine(E.Errors()))
    89  	}
    90  }
    91  
    92  var (
    93  	// Separator for single-line error messages.
    94  	_singlelineSeparator = []byte("; ")
    95  
    96  	// Prefix for multi-line messages
    97  	_multilinePrefix = []byte("the following errors occurred:")
    98  
    99  	// Prefix for the first and following lines of an item in a list of
   100  	// multi-line error messages.
   101  	//
   102  	// For example, if a single item is:
   103  	//
   104  	// 	foo
   105  	// 	bar
   106  	//
   107  	// It will become,
   108  	//
   109  	// 	 -  foo
   110  	// 	    bar
   111  	_multilineSeparator = []byte("\n -  ")
   112  	_multilineIndent    = []byte("    ")
   113  )
   114  
   115  func formatSingleLine(errs []error) []byte {
   116  	var buf bytes.Buffer
   117  	var first = true
   118  	for _, err := range errs {
   119  		if first {
   120  			first = false
   121  		} else {
   122  			buf.Write(_singlelineSeparator)
   123  		}
   124  		buf.WriteString(err.Error())
   125  	}
   126  	return buf.Bytes()
   127  }
   128  
   129  func formatMultiLine(errs []error) []byte {
   130  	var buf bytes.Buffer
   131  	buf.Write(_multilinePrefix)
   132  	for _, err := range errs {
   133  		buf.Write(_multilineSeparator)
   134  		s := fmt.Sprintf("%+v", err)
   135  		first := true
   136  		for len(s) > 0 {
   137  			if first {
   138  				first = false
   139  			} else {
   140  				buf.Write(_multilineIndent)
   141  			}
   142  			idx := strings.IndexByte(s, '\n')
   143  			if idx < 0 {
   144  				idx = len(s) - 1
   145  			}
   146  			buf.WriteString(s[:idx+1])
   147  			s = s[idx+1:]
   148  		}
   149  	}
   150  	return buf.Bytes()
   151  }