github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/throw/chain.go (about)

     1  // Copyright 2020 Insolar Network Ltd.
     2  // All rights reserved.
     3  // This material is licensed under the Insolar License version 1.0,
     4  // available at https://github.com/insolar/assured-ledger/blob/master/LICENSE.md.
     5  
     6  package throw
     7  
     8  import (
     9  	"errors"
    10  	"reflect"
    11  	"strings"
    12  )
    13  
    14  type DeepestStackMode uint8
    15  
    16  const (
    17  	_ DeepestStackMode = iota
    18  	InheritedTrace
    19  	SupersededTrace
    20  )
    21  
    22  type StackTraceHolder interface {
    23  	// Cause returns a cause for this stack trace. It can NOT be used like Unwrap() as it may return self.
    24  	Cause() error
    25  	// ShallowStackTrace returns a stack trace registered for this cause
    26  	ShallowStackTrace() StackTrace
    27  	// DeepestStackTrace returns this or the deepest stack trace from cause's error chain that embeds ShallowStackTrace.
    28  	// Bool value is true when the stack trace was inherited from the cause.
    29  	DeepestStackTrace() (StackTrace, DeepestStackMode)
    30  }
    31  
    32  // StackOf returns a target-matched entry with a wrapping StackTraceHolder (optional)
    33  // When (target) is nil - returns the last error of the chain
    34  // nolint
    35  func StackOf(errChain, target error) (error, StackTraceHolder, bool) {
    36  	isComparable := target == nil || reflect.TypeOf(target).Comparable()
    37  
    38  	for errChain != nil {
    39  		nextErr := errors.Unwrap(errChain)
    40  		if sw, ok := errChain.(StackTraceHolder); ok {
    41  			if target == nil && nextErr == nil || isThis(isComparable, sw.Cause(), target) {
    42  				return errChain, sw, true
    43  			}
    44  		} else if target == nil && nextErr == nil || isThis(isComparable, errChain, target) {
    45  			return errChain, nil, true
    46  		}
    47  		errChain = nextErr
    48  	}
    49  
    50  	return nil, nil, false
    51  }
    52  
    53  // NearestStackOf returns a target-matched entry with a last encountered StackTraceHolder with a either shallow or deep non-nil stack (optional)
    54  // When (target) is nil - returns the last error of the chain
    55  // When (target) is not found, returns only the last StackTraceHolder.
    56  // nolint
    57  func NearestStackOf(errChain, target error) (error, StackTraceHolder, bool) {
    58  	isComparable := target == nil || reflect.TypeOf(target).Comparable()
    59  
    60  	var sth StackTraceHolder
    61  	for errChain != nil {
    62  		nextErr := errors.Unwrap(errChain)
    63  		if sw, ok := errChain.(StackTraceHolder); ok {
    64  			if st, _ := sw.DeepestStackTrace(); st != nil {
    65  				sth = sw
    66  			}
    67  			if target == nil && nextErr == nil || isThis(isComparable, sw.Cause(), target) {
    68  				return errChain, sth, true
    69  			}
    70  		} else if target == nil && nextErr == nil || isThis(isComparable, errChain, target) {
    71  			return errChain, sth, true
    72  		}
    73  		errChain = nextErr
    74  	}
    75  
    76  	return nil, sth, false
    77  }
    78  
    79  func DeepestStackTraceOf(errChain error) StackTrace {
    80  	for errChain != nil {
    81  		if sw, ok := errChain.(StackTraceHolder); ok {
    82  			if st, _ := sw.DeepestStackTrace(); st != nil {
    83  				return st
    84  			}
    85  		}
    86  		errChain = errors.Unwrap(errChain)
    87  	}
    88  	return nil
    89  }
    90  
    91  func ErrorWithStack(errChain error) string {
    92  	return JoinStackText(errChain.Error(), DeepestStackTraceOf(errChain))
    93  }
    94  
    95  func ErrorWithTopOrFullStack(errChain error) (string, StackTrace) {
    96  	st := DeepestStackTraceOf(errChain)
    97  	if st == nil || st.IsFullStack() {
    98  		return errChain.Error(), st
    99  	}
   100  	return JoinStackText(errChain.Error(), st), nil
   101  }
   102  
   103  func ErrorWithTopOrMinimizedStack(errChain error, boundPackage string, includeBoundary bool) (string, StackTrace) {
   104  	st := DeepestStackTraceOf(errChain)
   105  	switch {
   106  	case st == nil:
   107  		return errChain.Error(), nil
   108  	case !st.IsFullStack():
   109  		return JoinStackText(errChain.Error(), st), nil
   110  	}
   111  	if boundPackage != "" {
   112  		st = MinimizeStackTrace(st, boundPackage, includeBoundary)
   113  	}
   114  	min := ExtractStackTop(st, 0)
   115  	return JoinStackText(errChain.Error(), min), st
   116  }
   117  
   118  // OutermostStack returns the closest StackTraceHolder with non-nil ShallowStackTrace from errChain
   119  func OutermostStack(errChain error) StackTraceHolder {
   120  	for errChain != nil {
   121  		if sw, ok := errChain.(StackTraceHolder); ok && sw.ShallowStackTrace() != nil {
   122  			return sw
   123  		}
   124  		errChain = errors.Unwrap(errChain)
   125  	}
   126  	return nil
   127  }
   128  
   129  // InnermostStack returns the most distant StackTraceHolder with non-nil ShallowStackTrace from errChain
   130  func InnermostStack(errChain error) (sth StackTraceHolder) {
   131  	for errChain != nil {
   132  		if sw, ok := errChain.(StackTraceHolder); ok && sw.ShallowStackTrace() != nil {
   133  			sth = sw
   134  		}
   135  		errChain = errors.Unwrap(errChain)
   136  	}
   137  	return
   138  }
   139  
   140  // InnermostFullStack returns the most distant StackTraceHolder with non-nil ShallowStackTrace and IsFullStack from errChain
   141  func InnermostFullStack(errChain error) (sth StackTraceHolder) {
   142  	for errChain != nil {
   143  		if sw, ok := errChain.(StackTraceHolder); ok && sw.ShallowStackTrace() != nil && sw.ShallowStackTrace().IsFullStack() {
   144  			sth = sw
   145  		}
   146  		errChain = errors.Unwrap(errChain)
   147  	}
   148  	return
   149  }
   150  
   151  // Walks through the given error chain and call (fn) for each entry, does unwrapping for stack trace data.
   152  func Walk(errChain error, fn func(error, StackTraceHolder) bool) bool {
   153  	if fn == nil {
   154  		panic(IllegalValue())
   155  	}
   156  
   157  	for errChain != nil {
   158  		if sw, ok := errChain.(StackTraceHolder); ok {
   159  			if fn(sw.Cause(), sw) {
   160  				return true
   161  			}
   162  			errChain = errors.Unwrap(errChain)
   163  		} else if fn(errChain, nil) {
   164  			return true
   165  		}
   166  		errChain = errors.Unwrap(errChain)
   167  	}
   168  	return false
   169  }
   170  
   171  // PrintTo calls Walk() and builds a full list of errors and corresponding stacks in the chain.
   172  func PrintTo(errChain error, includeStack bool, b *strings.Builder) {
   173  	Walk(errChain, func(err error, traceHolder StackTraceHolder) bool {
   174  		var trace StackTrace
   175  		if traceHolder != nil {
   176  			trace = traceHolder.ShallowStackTrace()
   177  		}
   178  		switch {
   179  		case err != nil:
   180  			b.WriteString(err.Error())
   181  			b.WriteByte('\n')
   182  			if trace == nil || !includeStack {
   183  				return false
   184  			}
   185  			b.WriteString(stackTracePrintPrefix)
   186  		case trace != nil && includeStack:
   187  			b.WriteString("<nil>\n" + stackTracePrintPrefix)
   188  		default:
   189  			b.WriteString("<nil>\n")
   190  			return false
   191  		}
   192  
   193  		if err := trace.WriteStackTraceTo(b); err != nil {
   194  			panic(err)
   195  		}
   196  		b.WriteByte('\n')
   197  		return false
   198  	})
   199  }
   200  
   201  // Print is a convenience wrapper for PrintTo()
   202  func Print(errChain error) string {
   203  	if errChain == nil {
   204  		return ""
   205  	}
   206  	b := strings.Builder{}
   207  	PrintTo(errChain, true, &b)
   208  	return b.String()
   209  }
   210  
   211  // IsEqual does panic-safe comparison of error values. Incomparable values will always return false.
   212  // It uses Is() on both sides
   213  func IsEqual(err0, err1 error) bool {
   214  	if err0 == nil || err1 == nil {
   215  		return err0 == err1
   216  	}
   217  	if reflect.TypeOf(err1).Comparable() && err0 == err1 {
   218  		return true
   219  	}
   220  	if x, ok := err0.(interface{ Is(error) bool }); ok && x.Is(err1) {
   221  		return true
   222  	}
   223  	if x, ok := err1.(interface{ Is(error) bool }); ok && x.Is(err0) {
   224  		return true
   225  	}
   226  	return false
   227  }
   228  
   229  type iser interface {
   230  	Is(error) bool
   231  }
   232  
   233  func isThis(isComparable bool, err, target error) bool {
   234  	if isComparable && err == target {
   235  		return true
   236  	}
   237  	if x, ok := err.(iser); ok && x.Is(target) {
   238  		return true
   239  	}
   240  	return false
   241  }