github.com/influx6/npkg@v0.8.8/nerror/errors.go (about) 1 package nerror 2 3 import ( 4 "bytes" 5 "fmt" 6 "reflect" 7 "strings" 8 "sync" 9 10 "github.com/influx6/npkg" 11 "github.com/influx6/npkg/nframes" 12 "github.com/influx6/npkg/njson" 13 ) 14 15 type IsError interface { 16 IsErr(err error) bool 17 } 18 19 // IsAny returns true/false any of giving error matches set of error. 20 func IsAny(err error, set ...error) bool { 21 err = UnwrapDeep(err) 22 for _, item := range set { 23 if isErrItem, ok := item.(IsError); ok { 24 if !isErrItem.IsErr(err) { 25 continue 26 } 27 return true 28 } 29 if IsSameTypeName(err, item) { 30 return true 31 } 32 if item == err { 33 return true 34 } 35 } 36 return false 37 } 38 39 // ErrorStack allows you to stack multiple messages together 40 // which can be combined into a single error message when 41 // calling ErrorStack.Err(). 42 // 43 // If no messages are found then no error is returned. 44 type ErrorStack []string 45 46 func (es *ErrorStack) Add(msg string, m ...interface{}) { 47 if len(m) > 0 { 48 *es = append(*es, "- "+fmt.Sprintf(msg, m...)+"\n") 49 return 50 } 51 *es = append(*es, "- "+msg+"\n") 52 } 53 54 func (es *ErrorStack) Err() error { 55 if len(*es) > 0 { 56 return New(strings.Join(*es, "")) 57 } 58 return nil 59 } 60 61 // ErrorOption defines a giving function which receiving 62 // a giving error returns another modified with respect 63 // to it's internal intent. 64 type ErrorOption func(error) error 65 66 // Apply applies giving set of ErrorOptions to provided error. 67 func Apply(err error, ops ...ErrorOption) error { 68 for _, op := range ops { 69 err = op(err) 70 } 71 return err 72 } 73 74 // Frames will attempt to add call stack into provided error if 75 // error is a PointingError type without a stack. 76 func Frames(frames nframes.Frames) ErrorOption { 77 return func(e error) error { 78 pe := unwrapAs(e) 79 pe.Frames = frames.Details() 80 return pe 81 } 82 } 83 84 // Meta adds giving header map as meta information to error. 85 func Meta(err error, header map[string]interface{}) ErrorOption { 86 return func(e error) error { 87 pe := unwrapAs(e) 88 pe.Meta = header 89 return pe 90 } 91 } 92 93 // Stacked returns an error from provided message and parameter 94 // list if provided. It adds necessary information related 95 // to point of return. 96 func Stacked() ErrorOption { 97 return func(e error) error { 98 next := unwrapAs(e) 99 next.Frames = nframes.GetFrameDetails(3, 32) 100 return next 101 } 102 } 103 104 // StackedBy returns an error from provided message and parameter 105 // list if provided. It adds necessary information related 106 // to point of return. 107 func StackedBy(n int) ErrorOption { 108 return func(e error) error { 109 next := unwrapAs(e) 110 next.Frames = nframes.GetFrameDetails(3, 32) 111 return next 112 } 113 } 114 115 // StackWrap returns a new error which wraps existing error value if 116 // present and also collects current stack trace into returned error. 117 // It formats message accordingly with arguments from 118 // variadic list v. 119 func StackWrap(err error, message string, v ...interface{}) *PointingError { 120 if len(v) != 0 { 121 message = fmt.Sprintf(message, v...) 122 } 123 124 var next PointingError 125 next.Parent = err 126 next.Message = message 127 next.Frames = nframes.GetFrameDetails(3, 32) 128 return &next 129 } 130 131 // NewStack returns a new error which wraps existing error value if 132 // present and also collects current stack trace into returned error. 133 // It formats message accordingly with arguments from 134 // variadic list v. 135 func NewStack(message string, v ...interface{}) *PointingError { 136 if len(v) != 0 { 137 message = fmt.Sprintf(message, v...) 138 } 139 140 var next PointingError 141 next.Message = message 142 next.Frames = nframes.GetFrameDetails(3, 32) 143 return &next 144 } 145 146 // New returns an error from provided message and parameter 147 // list if provided. It adds necessary information related 148 // to point of return. 149 func New(message string, v ...interface{}) *PointingError { 150 if len(v) != 0 { 151 message = fmt.Sprintf(message, v...) 152 } 153 154 var next PointingError 155 next.Message = message 156 next.Frames = nframes.GetFrameDetails(3, 32) 157 return &next 158 } 159 160 // NewBy returns an error from provided message and parameter 161 // list if provided. It adds necessary information related 162 // to point of return. 163 func NewBy(n int, message string, v ...interface{}) *PointingError { 164 if len(v) != 0 { 165 message = fmt.Sprintf(message, v...) 166 } 167 168 var next PointingError 169 next.Message = message 170 next.Frames = nframes.GetFrameDetails(3, n) 171 return &next 172 } 173 174 // WrapBy returns a new error which wraps existing error value if 175 // present. It formats message accordingly with arguments from 176 // variadic list v. 177 func WrapBy(n int, err error, message string, v ...interface{}) *PointingError { 178 if len(v) != 0 { 179 message = fmt.Sprintf(message, v...) 180 } 181 182 var next PointingError 183 next.Parent = err 184 next.Message = message 185 next.Frames = nframes.GetFrameDetails(3, n) 186 return &next 187 } 188 189 // WrapOnly returns a new error which wraps existing error value if 190 // present. 191 func WrapOnly(err error) *PointingError { 192 if tm, ok := err.(*PointingError); ok { 193 return tm 194 } 195 return wrapOnlyBy(err, 4, 32) 196 } 197 198 // Unwrap returns the underline error of giving PointingError. 199 func Unwrap(e error) error { 200 if tm, ok := e.(*PointingError); ok { 201 if tm.Parent == nil { 202 return tm 203 } 204 return tm.Parent 205 } 206 return e 207 } 208 209 // UnwrapDeep returns the root error wrapped by all possible PointingError types. 210 // It attempts to retrieve the original error. 211 func UnwrapDeep(e error) error { 212 if tm, ok := e.(*PointingError); ok { 213 if tm.Parent == nil { 214 return tm 215 } 216 217 return UnwrapDeep(tm.Parent) 218 } 219 220 return e 221 } 222 223 // Forward wraps giving error, recording where it was 224 // created and attaches the frames of call. 225 func Forward(err error) *PointingError { 226 next := wrapOnly(err) 227 next.Parent = err 228 next.Message = err.Error() 229 next.Frames = nframes.GetFrameDetails(3, 32) 230 return next 231 } 232 233 // Wrap returns a new error which wraps existing error value if 234 // present. It formats message accordingly with arguments from 235 // variadic list v. 236 func Wrap(err error, message string, v ...interface{}) *PointingError { 237 if len(v) != 0 { 238 message = fmt.Sprintf(message, v...) 239 } 240 241 next := wrapOnly(err) 242 next.Parent = err 243 next.Message = message 244 next.Frames = nframes.GetFrameDetails(3, 32) 245 return next 246 } 247 248 // wrapOnly returns a new error which wraps existing error value if 249 // present. 250 func wrapOnly(err error) *PointingError { 251 return wrapOnlyBy(err, 4, 32) 252 } 253 254 // WrapOnlyBy returns a new error which wraps existing error value if 255 // present. 256 func wrapOnlyBy(err error, depth int, stack int) *PointingError { 257 var next PointingError 258 next.Parent = err 259 next.Frames = nframes.GetFrameDetails(depth, stack) 260 return &next 261 } 262 263 // unwrapAs unwraps giving error to PointingError if it is else wraps 264 // and returns a new PointingError. 265 func unwrapAs(e error) *PointingError { 266 if tm, ok := e.(*PointingError); ok { 267 return tm 268 } 269 return wrapOnlyBy(e, 4, 32) 270 } 271 272 var _ ErrorMessage = (*PointingError)(nil) 273 var _ HasMessage = (*PointingError)(nil) 274 275 // PointingError defines a custom error type which points to 276 // both an originating point of return and a parent error if 277 // wrapped. 278 type PointingError struct { 279 Message string 280 Params map[string]string 281 Frames []nframes.FrameDetail 282 Meta map[string]interface{} 283 Parent error 284 } 285 286 func (pe *PointingError) Add(key, value string) *PointingError { 287 if pe.Params == nil { 288 pe.Params = map[string]string{} 289 } 290 pe.Params[key] = value 291 return pe 292 } 293 294 func (pe *PointingError) IsError(err error) bool { 295 if pe.Parent == nil { 296 return false 297 } 298 if isErrorParent, ok := pe.Parent.(IsError); ok { 299 return isErrorParent.IsErr(err) 300 } 301 if IsSameTypeName(pe.Parent, err) { 302 return true 303 } 304 return pe.Parent == err 305 } 306 307 // Error implements the error interface. 308 func (pe *PointingError) Error() string { 309 return pe.String() 310 } 311 312 type HasMessage interface { 313 HasMessage() bool 314 } 315 316 func (pe PointingError) HasMessage() bool { 317 return len(pe.Message) > 0 318 } 319 320 type ErrorMessage interface { 321 GetMessage() string 322 } 323 324 func (pe *PointingError) GetMessage() string { 325 if len(pe.Message) == 0 && pe.Parent != nil { 326 if pep, ok := pe.Parent.(ErrorMessage); ok { 327 return pep.GetMessage() 328 } 329 } 330 return pe.Message 331 } 332 333 // String returns formatted string. 334 func (pe *PointingError) String() string { 335 var buf = bufPool.Get().(*bytes.Buffer) 336 defer bufPool.Put(buf) 337 338 pe.Format(buf) 339 return buf.String() 340 } 341 342 var bufPool = sync.Pool{ 343 New: func() interface{} { 344 return bytes.NewBuffer(make([]byte, 0, 128)) 345 }, 346 } 347 348 // FormatMessage formats giving message of an error. 349 func (pe *PointingError) FormatMessage(buf *bytes.Buffer) { 350 if pe.Message != "" { 351 buf.WriteString(pe.Message) 352 } 353 354 if pe.Parent != nil { 355 if peHas, ok := pe.Parent.(HasMessage); ok && peHas.HasMessage() { 356 buf.WriteString(": ") 357 } 358 if pem, ok := pe.Parent.(*PointingError); ok { 359 pem.FormatMessage(buf) 360 } else { 361 buf.WriteString(pe.Parent.Error()) 362 } 363 } 364 } 365 366 // FormatStack formats giving stack information for giving error. 367 func (pe *PointingError) FormatStack(buf *bytes.Buffer) { 368 buf.WriteString("-------------------------------------------") 369 var nb = njson.JSONB() 370 if len(pe.Params) > 0 { 371 buf.WriteString("\n") 372 npkg.EncodableStringMap(pe.Params).EncodeObject(nb) 373 _, _ = nb.WriteTo(buf) 374 buf.WriteString("\n") 375 buf.WriteString("-------------------------------------------") 376 } 377 buf.WriteString("\n") 378 for _, frame := range pe.Frames { 379 _, _ = fmt.Fprintf(buf, "- [%s] %s:%d", frame.Package, frame.File, frame.Line) 380 buf.WriteString("\n") 381 } 382 if po, ok := pe.Parent.(*PointingError); ok { 383 po.FormatStack(buf) 384 } 385 } 386 387 // Format writes details of error into provided buffer. 388 func (pe *PointingError) Format(buf *bytes.Buffer) { 389 pe.FormatMessage(buf) 390 buf.WriteString("\n") 391 pe.FormatStack(buf) 392 } 393 394 func IsSameTypeName(me interface{}, other interface{}) bool { 395 var mc = reflect.TypeOf(me) 396 var oc = reflect.TypeOf(other) 397 return nameOfType(mc) == nameOfType(oc) 398 } 399 func nameOfType(t reflect.Type) string { 400 stars := "" 401 if t == nil { 402 return "nil" 403 } 404 405 for t.Kind() == reflect.Ptr { 406 stars += "*" 407 t = t.Elem() 408 } 409 410 if t.Kind() == reflect.Interface { 411 stars = "" 412 } 413 return t.PkgPath() + "/" + stars + t.Name() 414 }