github.com/jbendotnet/noms@v0.0.0-20190904222105-c43e4293ea92/go/d/try.go (about)

     1  // Copyright 2016 Attic Labs, Inc. All rights reserved.
     2  // Licensed under the Apache License, version 2.0:
     3  // http://www.apache.org/licenses/LICENSE-2.0
     4  
     5  // Package d implements several debug, error and assertion functions used throughout Noms.
     6  package d
     7  
     8  import (
     9  	"errors"
    10  	"fmt"
    11  	"reflect"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  )
    15  
    16  // d.Chk.<Method>() -- used in test cases and as assertions
    17  var (
    18  	Chk = assert.New(&panicker{})
    19  )
    20  
    21  type panicker struct {
    22  }
    23  
    24  func (s panicker) Errorf(format string, args ...interface{}) {
    25  	panic(fmt.Sprintf(format, args...))
    26  }
    27  
    28  // Panic(err) creates an error using format and args and wraps it in a
    29  // WrappedError which can be handled using Try() and TryCatch()
    30  func Panic(format string, args ...interface{}) {
    31  
    32  	if len(args) == 0 {
    33  		err := errors.New(format)
    34  		panic(Wrap(err))
    35  	}
    36  	err := fmt.Errorf(format, args...)
    37  	panic(Wrap(err))
    38  }
    39  
    40  // PanicIfError(err) && PanicIfTrue(expr) can be used to panic in a way that's
    41  // easily handled by Try() and TryCatch()
    42  func PanicIfError(err error) {
    43  	if err != nil {
    44  		panic(Wrap(err))
    45  	}
    46  }
    47  
    48  // If b is true, creates a default error, wraps it and panics.
    49  func PanicIfTrue(b bool) {
    50  	if b {
    51  		panic(Wrap(errors.New("Expected true")))
    52  	}
    53  }
    54  
    55  // If b is false, creates a default error, wraps it and panics.
    56  func PanicIfFalse(b bool) {
    57  	if !b {
    58  		panic(Wrap(errors.New("Expected false")))
    59  	}
    60  }
    61  
    62  // If 'f' panics with a WrappedError then recover that error.
    63  // If types is empty, return the WrappedError.
    64  // if types is not empty and cause is not one of the listed types, re-panic.
    65  // if types is not empty and cause is one of the types, return 'cause'
    66  func Try(f func(), types ...interface{}) (err error) {
    67  	defer recoverWrappedTypes(&err, types)
    68  	f()
    69  	return
    70  }
    71  
    72  // If 'f' panics with a WrappedError then recover that error and return it.
    73  // If types is empty, return the WrappedError.
    74  // if types is not empty and cause is not one of the listed types, re-panic.
    75  // if types is not empty and cause is one of the types, return 'cause'
    76  func TryCatch(f func(), catch func(err error) error) (err error) {
    77  	defer recoverWrapped(&err, catch)
    78  	f()
    79  	return
    80  }
    81  
    82  type WrappedError interface {
    83  	Error() string
    84  	Cause() error
    85  }
    86  
    87  // Wraps an error. The enclosing error has a default Error() that contains the error msg along
    88  // with a backtrace. The original error can be retrieved by calling err.Cause().
    89  func Wrap(err error) WrappedError {
    90  	if err == nil {
    91  		return nil
    92  	}
    93  	if we, ok := err.(WrappedError); ok {
    94  		return we
    95  	}
    96  
    97  	st := stackTracer{}
    98  	assert := assert.New(&st)
    99  	assert.Fail(err.Error())
   100  
   101  	return wrappedError{st.stackTrace, err}
   102  }
   103  
   104  // If err is a WrappedError, then Cause() is returned, otherwise returns err.
   105  func Unwrap(err error) error {
   106  	cause := err
   107  	we, ok := err.(WrappedError)
   108  	if ok {
   109  		cause = we.Cause()
   110  	}
   111  	return cause
   112  }
   113  
   114  func causeInTypes(err error, types ...interface{}) bool {
   115  	cause := Unwrap(err)
   116  	typ := reflect.TypeOf(cause)
   117  	for _, t := range types {
   118  		if typ == reflect.TypeOf(t) {
   119  			return true
   120  		}
   121  	}
   122  	return false
   123  }
   124  
   125  // Utility method, that checks type of error and panics with wrapped error not one of the listed types.
   126  func PanicIfNotType(err error, types ...interface{}) error {
   127  	if err == nil {
   128  		return nil
   129  	}
   130  	if !causeInTypes(err, types...) {
   131  		we, ok := err.(WrappedError)
   132  		if !ok {
   133  			we = Wrap(err)
   134  		}
   135  		panic(we)
   136  	}
   137  	return Unwrap(err)
   138  }
   139  
   140  type wrappedError struct {
   141  	msg   string
   142  	cause error
   143  }
   144  
   145  func (we wrappedError) Error() string { return we.msg }
   146  func (we wrappedError) Cause() error  { return we.cause }
   147  
   148  type stackTracer struct {
   149  	stackTrace string
   150  }
   151  
   152  func (s *stackTracer) Errorf(format string, args ...interface{}) {
   153  	s.stackTrace = fmt.Sprintf(format, args...)
   154  }
   155  
   156  func recoverWrappedTypes(errp *error, types []interface{}) {
   157  	if r := recover(); r != nil {
   158  		if wrapper, ok := r.(wrappedError); !ok {
   159  			panic(r)
   160  		} else if len(types) > 0 && !causeInTypes(wrapper, types...) {
   161  			panic(r)
   162  		} else if len(types) > 0 {
   163  			*errp = wrapper.Cause()
   164  		} else {
   165  			*errp = wrapper
   166  		}
   167  	}
   168  }
   169  
   170  func recoverWrapped(errp *error, catch func(err error) error) {
   171  	if r := recover(); r != nil {
   172  		we, ok := r.(wrappedError)
   173  		if !ok {
   174  			panic(r)
   175  		}
   176  		if catch != nil {
   177  			*errp = catch(we)
   178  		} else {
   179  			*errp = Unwrap(we)
   180  		}
   181  	}
   182  }