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

     1  /*
     2  © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/)
     3  ISC License
     4  */
     5  
     6  package perrors
     7  
     8  import (
     9  	"errors"
    10  	"reflect"
    11  	"slices"
    12  
    13  	"github.com/haraldrudell/parl/perrors/errorglue"
    14  	"github.com/haraldrudell/parl/pruntime"
    15  )
    16  
    17  const e116PackFuncStackFrames = 1
    18  
    19  // ErrorData returns any embedded data values from err and its error chain as a list and map
    20  //   - list contains values where key was empty, oldest first
    21  //   - keyValues are string values associated with a key string, overwriting older values
    22  //   - err list keyValues may be nil
    23  func ErrorData(err error) (list []string, keyValues map[string]string) {
    24  
    25  	// traverse the err and its error chain, newest first
    26  	//	- only errors with data matter
    27  	for ; err != nil; err = errors.Unwrap(err) {
    28  
    29  		// ignore errrors without key/value pair
    30  		var e, ok = err.(errorglue.ErrorHasData)
    31  		if !ok {
    32  			continue
    33  		}
    34  
    35  		// empty key is appended to slice
    36  		//	- oldest value first
    37  		var key, value = e.KeyValue()
    38  		if key == "" { // for the slice
    39  			list = append(list, value) // newest first
    40  			continue
    41  		}
    42  
    43  		// for the map
    44  		if keyValues == nil {
    45  			keyValues = map[string]string{key: value}
    46  			continue
    47  		}
    48  		// values are added newset first
    49  		//	- do not overwrite newer values with older
    50  		if _, ok := keyValues[key]; !ok {
    51  			keyValues[key] = value
    52  		}
    53  	}
    54  	slices.Reverse(list) // oldest first
    55  
    56  	return
    57  }
    58  
    59  // ErrorList returns all error instances from a possible error chain.
    60  // — If err is nil an empty slice is returned.
    61  // — If err does not have associated errors, a slice of err, length 1, is returned.
    62  // — otherwise, the first error of the returned slice is err followed by
    63  //
    64  //		other errors oldest first.
    65  //	- Cyclic error values are dropped
    66  func ErrorList(err error) (errs []error) {
    67  	return errorglue.ErrorList(err)
    68  }
    69  
    70  // HasStack detects if the error chain already contains a stack trace
    71  func HasStack(err error) (hasStack bool) {
    72  	if err == nil {
    73  		return
    74  	}
    75  	var e errorglue.ErrorCallStacker
    76  	return errors.As(err, &e)
    77  }
    78  
    79  // IsWarning determines if an error has been flagged as a warning.
    80  func IsWarning(err error) (isWarning bool) {
    81  	for ; err != nil; err = errors.Unwrap(err) {
    82  		if _, isWarning = err.(*errorglue.WarningType); isWarning {
    83  			return // is warning
    84  		}
    85  	}
    86  	return // not a warning
    87  }
    88  
    89  // error116.PackFunc returns the package name and function name
    90  // of the caller:
    91  //
    92  //	error116.FuncName
    93  func PackFunc() (packageDotFunction string) {
    94  	var frames = 1 // cpunt PackFunc frame
    95  	return PackFuncN(frames)
    96  }
    97  
    98  func PackFuncN(skipFrames int) (packageDotFunction string) {
    99  	if skipFrames < 0 {
   100  		skipFrames = 0
   101  	}
   102  	var cL = pruntime.NewCodeLocation(e116PackFuncStackFrames + skipFrames)
   103  	packageDotFunction = cL.Name()
   104  	if pack := cL.Package(); pack != "main" {
   105  		packageDotFunction = pack + "." + packageDotFunction
   106  	}
   107  	return
   108  }
   109  
   110  // ErrpString returns the error message possibly
   111  // contained in *errp
   112  //   - if errp or *errp is nil: “OK”
   113  func ErrpString(errp *error) (s string) {
   114  	var err error
   115  	if errp != nil {
   116  		err = *errp
   117  	}
   118  	if err == nil {
   119  		s = "OK"
   120  		return
   121  	}
   122  	s = err.Error()
   123  	return
   124  }
   125  
   126  // LongShort picks output format
   127  //   - no error: “OK”
   128  //   - no panic: error-message at runtime.gopanic:26
   129  //   - panic: long format with all stack traces and values
   130  //   - associated errors are always printed
   131  func LongShort(err error) (message string) {
   132  	var format errorglue.CSFormat
   133  	if err == nil {
   134  		format = errorglue.ShortFormat
   135  	} else if isPanic, _, _, _ := IsPanic(err); isPanic {
   136  		format = errorglue.LongFormat
   137  	} else {
   138  		format = errorglue.ShortFormat
   139  	}
   140  	message = errorglue.ChainString(err, format)
   141  
   142  	return
   143  }
   144  
   145  // perrors.Short gets a one-line location string similar to printf %-v and ShortFormat.
   146  // Short() does not print stack traces, data and associated errors.
   147  // Short() does print a one-liner of the error message and a brief code location:
   148  //
   149  //	error-message at error116.(*csTypeName).FuncName-chainstring_test.go:26
   150  func Short(err error) string {
   151  	return errorglue.ChainString(err, errorglue.ShortFormat)
   152  }
   153  
   154  // Deferr invokes a printing function for an error pointer.
   155  // Deferr returns the message.
   156  // Deferr is deferrable.
   157  // A colon and a space is appended to label.
   158  // If *errp is nil, OK is printed.
   159  // If errp is nil, a message is printed.
   160  // if fn is nil, nothing is printed.
   161  // If *errp contains an error it is printed in Short form:
   162  //
   163  //	label: Error message at error116.(*csTypeName).FuncName-chainstring_test.go:26
   164  func Deferr(label string, errp *error, fn func(format string, a ...interface{})) string {
   165  	if errp == nil {
   166  		return "perrors.Deferr: errp nil"
   167  	}
   168  	if label != "" {
   169  		label += ":\x20"
   170  	}
   171  	s := label + errorglue.ChainString(*errp, errorglue.ShortFormat)
   172  	if fn != nil {
   173  		fn(s)
   174  	}
   175  
   176  	return s
   177  }
   178  
   179  // error116.Long() gets a comprehensive string representation similar to printf %+v and LongFormat.
   180  // ShortFormat does not print stack traces, data and associated errors.
   181  // Long() prints full stack traces, string key-value and list values for both the error chain
   182  // of err, and associated errors and their chains
   183  //
   184  //	error-message
   185  //	  github.com/haraldrudell/parl/error116.(*csTypeName).FuncName
   186  //	    /opt/sw/privates/parl/error116/chainstring_test.go:26
   187  //	  runtime.goexit
   188  //	    /opt/homebrew/Cellar/go/1.17.8/libexec/src/runtime/asm_arm64.s:1133
   189  func Long(err error) string {
   190  	return errorglue.ChainString(err, errorglue.LongFormat)
   191  }
   192  
   193  /*
   194  IsType determines if the chain of err contains an error of type target.
   195  IsType is different from errors.Is in that IsType matches the type of err,
   196  not its value.
   197  IsType is different from errors.Is in that it works for error implementations missing
   198  the Is() method.
   199  IsType uses reflection.
   200  pointerToErrorValue argument is a pointer to an error implementation value, ie:
   201  
   202  	if the target struct has pointer reciever, the argument type *targetStruct
   203  	if the target struct has value receiver, the argument type targetStruct
   204  */
   205  func IsType(err error, pointerToErrorValue interface{}) (hadErrpType bool) {
   206  
   207  	// ensure pointerToErrorValue is non-nil pointer
   208  	// reflection returns nil for nil pointer
   209  	if pointerToErrorValue == nil {
   210  		panic(New("perrors.IsType: pointerToErrorValue nil"))
   211  	}
   212  
   213  	// ensure pointerToErrorValue is pointer
   214  	pointerType := reflect.TypeOf(pointerToErrorValue)
   215  	if pointerType.Kind() != reflect.Ptr {
   216  		panic(New("perrors.IsType: pointerToErrorValue not pointer"))
   217  	}
   218  
   219  	// get the error implementation type we are looking for
   220  	// this is what pointerToErrorValue points to
   221  	targetType := pointerType.Elem()
   222  
   223  	// traverse err’s error chain
   224  	for ; err != nil; err = errors.Unwrap(err) {
   225  
   226  		// get the type assigned to err interface
   227  		errType := reflect.TypeOf(err)
   228  
   229  		// check if the err type is the one we are looking for
   230  		if errType == targetType {
   231  			reflect.Indirect(reflect.ValueOf(pointerToErrorValue)).Set(reflect.ValueOf(err))
   232  			return true // err match exit
   233  		}
   234  
   235  		// also check for what err points to
   236  		if errType.Kind() == reflect.Ptr {
   237  			errPointsToType := errType.Elem()
   238  			if errPointsToType == targetType {
   239  				reflect.Indirect(reflect.ValueOf(pointerToErrorValue)).Set(reflect.Indirect(reflect.ValueOf(err)))
   240  				return true // *err match exit
   241  			}
   242  		}
   243  	}
   244  	return // no match exit
   245  }
   246  
   247  func Error0(err error) (e error) {
   248  	for ; err != nil; err = errors.Unwrap(err) {
   249  		e = err
   250  	}
   251  	return
   252  }
   253  
   254  // IsError determines if err represents error condition for all error implementations
   255  //   - eg. unix.Errno that is uintptr
   256  func IsError[T error](err T) (isError bool) {
   257  
   258  	// err is interface, obtain the runtime type and check for nil runtime value
   259  	var reflectValue = reflect.ValueOf(err)
   260  	if !reflectValue.IsValid() {
   261  		return // err interface has nil runtime-value return: false
   262  	}
   263  
   264  	// if err is not a zero-value, it is eror condition
   265  	isError = !reflectValue.IsZero()
   266  
   267  	return
   268  }