github.com/xmidt-org/webpa-common@v1.11.9/xerrors/xerrors.go (about)

     1  package xerrors
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"reflect"
     7  )
     8  
     9  func getError(unknown interface{}) error {
    10  	return findErrorInValue(reflect.ValueOf(unknown))
    11  }
    12  
    13  // findErrorInValue will try and genereate and error object either from the object itself if it can be created or its
    14  // fields.
    15  func findErrorInValue(v reflect.Value) error {
    16  	if v.Kind() == reflect.Invalid {
    17  		return nil
    18  	}
    19  
    20  	if v.CanInterface() {
    21  		// the object can be created, hopefully we can cast it to an error.
    22  		if _, ok := v.Interface().(*error); ok {
    23  			return findErrorInValue(v.Elem())
    24  		} else if err, ok := v.Interface().(error); ok {
    25  			if v.Type().String() == "*errors.errorString" {
    26  				return err
    27  			}
    28  			if v.Kind() == reflect.Interface {
    29  				return err
    30  			} else if v.Kind() == reflect.Ptr {
    31  				return findErrorInValue(v.Elem())
    32  			}
    33  			return err
    34  		} else if v.Kind() == reflect.Ptr { // We can't cast it to an error, check the pointer.
    35  			return findErrorInValue(v.Elem())
    36  		} else if v.Kind() == reflect.Struct { // We can't cast it to an error, check the fields.
    37  			return iterateFields(v)
    38  		}
    39  	} else if v.Kind() == reflect.Ptr && v.Type().String() == "*errors.errorString" { // the base case. AKA errors.New("error")
    40  		return errors.New(fmt.Sprintf("%s", v.Elem().Field(0)))
    41  	} else if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { // try what the object contains.
    42  		return findErrorInValue(v.Elem())
    43  	} else if v.Kind() == reflect.Struct { // try the fields
    44  		return iterateFields(v)
    45  	}
    46  
    47  	// an error could not be created
    48  	return nil
    49  }
    50  
    51  // FirstCause will find the first cause of the error. This is making the assumption that the errors are cascaded.
    52  // aka an Error struct will contain an error and so on.
    53  func FirstCause(err error) error {
    54  	return findRootError(err)
    55  }
    56  
    57  // findRootError will iterate over all the fields and the fields of those fields to generate an error. If no error is
    58  // found <nil> will be returned
    59  func findRootError(unknown interface{}) error {
    60  	if unknown == nil {
    61  		return nil
    62  	}
    63  
    64  	v := reflect.ValueOf(unknown)
    65  	currentLevel := findErrorInValue(v)
    66  	var tempErr error
    67  
    68  	if v.Kind() == reflect.Ptr { // check if itself and then the fields
    69  		e := findErrorInValue(v.Elem())
    70  		if e != nil {
    71  			e = findRootError(e)
    72  			if e != nil {
    73  				tempErr = e
    74  			}
    75  		}
    76  	} else if v.Kind() == reflect.Struct { // check the fields
    77  		e := iterateFields(v)
    78  		if e != nil {
    79  			e = findRootError(e)
    80  			if e != nil {
    81  				tempErr = e
    82  			}
    83  		}
    84  	}
    85  
    86  	if tempErr != nil {
    87  		return tempErr
    88  	}
    89  
    90  	return currentLevel
    91  }
    92  
    93  // iterateFields iterates over the variables of the struct and tries to create an error object from the field.
    94  // :warning: only can be called where v.Kind() == reflect.Struct
    95  func iterateFields(v reflect.Value) error {
    96  	var tempErr error
    97  
    98  	for i := 0; i < v.NumField(); i++ {
    99  		f := v.Field(i)
   100  		e := findErrorInValue(f)
   101  		if e != nil {
   102  			tempErr = e
   103  		}
   104  		if f.CanInterface() { // try and create an error if we can turn it into an interface{}
   105  			e = findRootError(f.Interface())
   106  			if e != nil {
   107  				tempErr = e
   108  			}
   109  		} else if f.Kind() == reflect.Interface { // try the object the field contains
   110  			f = f.Elem()
   111  			e = findErrorInValue(f)
   112  			if e != nil {
   113  				tempErr = e
   114  			}
   115  		}
   116  	}
   117  
   118  	return tempErr
   119  }