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 }