github.com/haraldrudell/parl@v0.4.176/perrors/errorglue/publics.go (about)

     1  /*
     2  © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package errorglue
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"strings"
    12  
    13  	"github.com/haraldrudell/parl/pruntime"
    14  )
    15  
    16  /*
    17  // AllErrors obtains all error instances references by the err error.
    18  // This includes error instances in its error chain and those of any separate,
    19  // associated error instances.
    20  // If err is nil, the returned slice is empty.
    21  // Otherwise the first error of the slice is err followed by other errors, oldest first.
    22  // If err does not have an error chain or any associated error instances,
    23  // the returned slice contains only err.
    24  // Cyclic error instances are removed
    25  func AllErrors(err error) (errs []error) {
    26  	if err != nil {
    27  		errs = append(errs, err)
    28  	}
    29  
    30  	// traverse all found errors
    31  	errMap := map[error]bool{}
    32  	for errsIndex := 0; errsIndex < len(errs); errsIndex++ {
    33  
    34  		// avoid cyclic error graph
    35  		e := errs[errsIndex]
    36  		_, knownError := errMap[e]
    37  		if knownError {
    38  			continue
    39  		}
    40  		errMap[e] = true
    41  
    42  		// traverse the error chain except the first entry which is the error itself
    43  		errorChain := ErrorChainSlice(e)
    44  		for ecIndex := 1; ecIndex < len(errorChain); ecIndex++ {
    45  
    46  			// include a possible error list
    47  			errs = append(errs, ErrorList(errorChain[ecIndex])...)
    48  		}
    49  	}
    50  	return
    51  }
    52  */
    53  
    54  // ErrorChainSlice returns a slice of errors from a possible error chain.
    55  // If err is nil, an empty slice is returned.
    56  // If err does not have an error chain, a slice of only err is returned.
    57  // Otherwise, the slice lists each error in the chain starting with err at index 0 ending with the oldest error of the chain
    58  func ErrorChainSlice(err error) (errs []error) {
    59  	for err != nil {
    60  		errs = append(errs, err)
    61  		err = errors.Unwrap(err)
    62  	}
    63  	return
    64  }
    65  
    66  // ErrorsWithStack gets all errors in the err error chain
    67  // that has a stack trace.
    68  // Oldest innermost stack trace is returned first.
    69  // if not stack trace is present, the slice is empty
    70  func ErrorsWithStack(err error) (errs []error) {
    71  	for err != nil {
    72  		if _, ok := err.(ErrorCallStacker); ok {
    73  			errs = append([]error{err}, errs...)
    74  		}
    75  		err = errors.Unwrap(err)
    76  	}
    77  	return
    78  }
    79  
    80  // GetInnerMostStack gets the oldest stack trace in the error chain
    81  // or nil if no stack trace is present
    82  func GetInnerMostStack(err error) (stack pruntime.Stack) {
    83  
    84  	// find the innermost implementation of ErrorCallStacker interface
    85  	var e ErrorCallStacker
    86  	for ; err != nil; err = errors.Unwrap(err) {
    87  		if ecs, ok := err.(ErrorCallStacker); ok {
    88  			e = ecs
    89  		}
    90  	}
    91  	if e == nil {
    92  		return // no implementation found
    93  	}
    94  
    95  	stack = e.StackTrace()
    96  	return
    97  }
    98  
    99  // GetStackTrace gets the last stack trace
   100  func GetStackTrace(err error) (stack pruntime.Stack) {
   101  	for ; err != nil; err = errors.Unwrap(err) {
   102  		if ecs, ok := err.(ErrorCallStacker); ok {
   103  			stack = ecs.StackTrace()
   104  			return
   105  		}
   106  	}
   107  	return
   108  }
   109  
   110  // GetStacks gets a slice of all stack traces, oldest first
   111  func GetStacks(err error) (stacks []pruntime.Stack) {
   112  	for err != nil {
   113  		if e, ok := err.(ErrorCallStacker); ok {
   114  			stack := e.StackTrace()
   115  			stacks = append([]pruntime.Stack{stack}, stacks...)
   116  		}
   117  		err = errors.Unwrap(err)
   118  	}
   119  	return
   120  }
   121  
   122  // DumpChain retrieves a space-separated string of
   123  // error implementation type-names found in the error
   124  // chain of err.
   125  // err can be nil
   126  //
   127  //	fmt.Println(Stack(errors.New("an error")))
   128  //	*error116.errorStack *errors.errorString
   129  func DumpChain(err error) (typeNames string) {
   130  	var strs []string
   131  	for err != nil {
   132  		strs = append(strs, fmt.Sprintf("%T", err))
   133  		err = errors.Unwrap(err)
   134  	}
   135  	typeNames = strings.Join(strs, "\x20")
   136  	return
   137  }
   138  
   139  // DumpGo produces a newline-separated string of
   140  // type-names and Go-syntax found in the error
   141  // chain of err.
   142  // err can be nil
   143  func DumpGo(err error) (typeNames string) {
   144  	var strs []string
   145  	for ; err != nil; err = errors.Unwrap(err) {
   146  		strs = append(strs, fmt.Sprintf("%T %#[1]v", err))
   147  	}
   148  	typeNames = strings.Join(strs, "\n")
   149  	return
   150  }
   151  
   152  /*
   153  RecoverThread is a defer function for threads.
   154  On panic, the onError function is invoked with an error
   155  message that contains location information
   156  */
   157  func RecoverThread(label string, onError func(err error)) {
   158  	if onError == nil {
   159  		panic(fmt.Errorf("%s: onError func nil", pruntime.NewCodeLocation(1).PackFunc()))
   160  	}
   161  	if v := recover(); v != nil {
   162  		err, ok := v.(error)
   163  		if !ok {
   164  			err = fmt.Errorf("non-error value: %v", v)
   165  		}
   166  		onError(fmt.Errorf("%s: %w", label, err))
   167  	}
   168  }