github.com/sttk/sabi@v0.5.0/err.go (about)

     1  // Copyright (C) 2022-2023 Takayuki Sato. All Rights Reserved.
     2  // This program is free software under MIT License.
     3  // See the file LICENSE in this distribution for more details.
     4  
     5  package sabi
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  )
    11  
    12  // Err is a struct which represents an error with a reason.
    13  type Err struct {
    14  	reason any
    15  	cause  error
    16  }
    17  
    18  var ok = Err{}
    19  
    20  // Ok is a function which returns an Err of which reason is nil.
    21  func Ok() Err {
    22  	return ok
    23  }
    24  
    25  // NewErr is a function which creates a new Err with a specified reason and
    26  // an optional cause.
    27  // A reason is a struct of which name expresses what is a reason.
    28  func NewErr(reason any, cause ...error) Err {
    29  	var err Err
    30  	err.reason = reason
    31  
    32  	if len(cause) > 0 {
    33  		err.cause = cause[0]
    34  	}
    35  
    36  	notifyErr(err)
    37  
    38  	return err
    39  }
    40  
    41  // IsOk method checks whether this Err indicates no error.
    42  func (err Err) IsOk() bool {
    43  	return (err.reason == nil)
    44  }
    45  
    46  // IsNotOk method checks whether this Err indicates an error.
    47  func (err Err) IsNotOk() bool {
    48  	return (err.reason != nil)
    49  }
    50  
    51  // Reason method returns an err reaason struct.
    52  func (err Err) Reason() any {
    53  	return err.reason
    54  }
    55  
    56  // ReasonName method returns a name of a reason struct type.
    57  func (err Err) ReasonName() string {
    58  	if err.reason == nil {
    59  		return ""
    60  	}
    61  	t := reflect.TypeOf(err.reason)
    62  	if t.Kind() == reflect.Ptr {
    63  		t = t.Elem()
    64  	}
    65  	return t.Name()
    66  }
    67  
    68  // ReasonPackage method returns a package path of a reason struct type.
    69  func (err Err) ReasonPackage() string {
    70  	if err.reason == nil {
    71  		return ""
    72  	}
    73  	t := reflect.TypeOf(err.reason)
    74  	if t.Kind() == reflect.Ptr {
    75  		t = t.Elem()
    76  	}
    77  	return t.PkgPath()
    78  }
    79  
    80  // Cause method returns a causal error of this Err.
    81  func (err Err) Cause() error {
    82  	return err.cause
    83  }
    84  
    85  // Error method returns a string which expresses this error.
    86  func (err Err) Error() string {
    87  	if err.reason == nil {
    88  		return "{reason=nil}"
    89  	}
    90  
    91  	v := reflect.ValueOf(err.reason)
    92  	if v.Kind() == reflect.Ptr {
    93  		v = v.Elem()
    94  	}
    95  
    96  	t := v.Type()
    97  
    98  	s := "{reason=" + t.Name()
    99  
   100  	n := v.NumField()
   101  	for i := 0; i < n; i++ {
   102  		k := t.Field(i).Name
   103  
   104  		f := v.Field(i)
   105  		if f.CanInterface() {
   106  			s += ", " + k + "=" + fmt.Sprintf("%v", f.Interface())
   107  		}
   108  	}
   109  
   110  	if err.cause != nil {
   111  		s += ", cause=" + err.cause.Error()
   112  	}
   113  
   114  	s += "}"
   115  	return s
   116  }
   117  
   118  // Unwrap method returns an error which is wrapped in this error.
   119  func (err Err) Unwrap() error {
   120  	return err.cause
   121  }
   122  
   123  // Get method returns a parameter value of a specified name, which is one of
   124  // fields of the reason struct.
   125  // If the specified named field is not found in this Err and this cause is
   126  // also Err struct, this method digs hierarchically to find the field.
   127  func (err Err) Get(name string) any {
   128  	if err.reason == nil {
   129  		return nil
   130  	}
   131  
   132  	v := reflect.ValueOf(err.reason)
   133  	if v.Kind() == reflect.Ptr {
   134  		v = v.Elem()
   135  	}
   136  
   137  	f := v.FieldByName(name)
   138  	if f.IsValid() && f.CanInterface() {
   139  		return f.Interface()
   140  	}
   141  
   142  	if err.cause != nil {
   143  		t := reflect.TypeOf(err.cause)
   144  		_, ok := t.MethodByName("Reason")
   145  		if ok {
   146  			_, ok := t.MethodByName("Get")
   147  			if ok {
   148  				return err.cause.(Err).Get(name)
   149  			}
   150  		}
   151  	}
   152  
   153  	return nil
   154  }
   155  
   156  // Situation method returns a map containing the field names and values of this
   157  // reason struct and of this cause if it is also Err struct.
   158  func (err Err) Situation() map[string]any {
   159  	var m map[string]any
   160  
   161  	if err.reason == nil {
   162  		return m
   163  	}
   164  
   165  	v := reflect.ValueOf(err.reason)
   166  	if v.Kind() == reflect.Ptr {
   167  		v = v.Elem()
   168  	}
   169  
   170  	if err.cause != nil {
   171  		t := reflect.TypeOf(err.cause)
   172  		_, ok := t.MethodByName("Reason")
   173  		if ok {
   174  			_, ok := t.MethodByName("Situation")
   175  			if ok {
   176  				m = err.cause.(Err).Situation()
   177  			}
   178  		}
   179  	}
   180  
   181  	if m == nil {
   182  		m = make(map[string]any)
   183  	}
   184  
   185  	t := v.Type()
   186  
   187  	n := v.NumField()
   188  	for i := 0; i < n; i++ {
   189  		k := t.Field(i).Name
   190  
   191  		f := v.Field(i)
   192  		if f.CanInterface() { // false if field is not public
   193  			m[k] = f.Interface()
   194  		}
   195  	}
   196  
   197  	return m
   198  }
   199  
   200  // IfOk method executes an argument function if this Err indicates non error.
   201  // If this Err indicates some error, this method just returns this Err.
   202  func (err Err) IfOk(fn func() Err) Err {
   203  	if err.IsOk() {
   204  		return fn()
   205  	}
   206  	return err
   207  }