initial commit
This commit is contained in:
		
							
								
								
									
										46
									
								
								calltrace.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								calltrace.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					package adverr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CallTrace represents a call stack trace similar to Java's stack trace
 | 
				
			||||||
 | 
					type CallTrace struct {
 | 
				
			||||||
 | 
						frames *runtime.Frames
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Trace returns a new CallTrace starting from this call
 | 
				
			||||||
 | 
					// Use skip to skip the first entries in the trace
 | 
				
			||||||
 | 
					func Trace(skip int) *CallTrace {
 | 
				
			||||||
 | 
						if !TraceCallStack {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pc := make([]uintptr, CallStackLength)
 | 
				
			||||||
 | 
						n := runtime.Callers(skip+1, pc)
 | 
				
			||||||
 | 
						pc = pc[:n]
 | 
				
			||||||
 | 
						return &CallTrace{runtime.CallersFrames(pc)}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (ct *CallTrace) String() string {
 | 
				
			||||||
 | 
						if ct == nil {
 | 
				
			||||||
 | 
							return ""
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b := new(strings.Builder)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for frame, ok := ct.frames.Next(); ok; frame, ok = ct.frames.Next() {
 | 
				
			||||||
 | 
							b.WriteString("\tat ")
 | 
				
			||||||
 | 
							b.WriteString(frame.Function)
 | 
				
			||||||
 | 
							b.WriteString(" (")
 | 
				
			||||||
 | 
							b.WriteString(frame.File)
 | 
				
			||||||
 | 
							b.WriteString(":")
 | 
				
			||||||
 | 
							b.WriteString(strconv.Itoa(frame.Line))
 | 
				
			||||||
 | 
							b.WriteString(")")
 | 
				
			||||||
 | 
							b.WriteString("\n")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return b.String()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										34
									
								
								doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								doc.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
						Package adverr implements errors with call stack traces
 | 
				
			||||||
 | 
						as well as error templates for error equality
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Usage examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Creating templates:
 | 
				
			||||||
 | 
							var (
 | 
				
			||||||
 | 
								ErrDoStuffFailed = adverr.NewErrTmpl("ErrDoStuffFailed", "Could'nt do stuff because of %s")
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Creating independent error (without error template):
 | 
				
			||||||
 | 
							func doStuffWithIndependentErr() error {
 | 
				
			||||||
 | 
								return adverr.New("Could'nt do stuff")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Creating error based on template:
 | 
				
			||||||
 | 
							func doStuff() error {
 | 
				
			||||||
 | 
								return ErrDoStuffFailed.New("reasons")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Printing errors on stderr convieniently:
 | 
				
			||||||
 | 
							Print(myErr)
 | 
				
			||||||
 | 
							Println(myErr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Printing errors on stderr and exit with exitcode:
 | 
				
			||||||
 | 
							Fatal(myErr, 1)
 | 
				
			||||||
 | 
							Fatalln(myErr, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Advantages of error templates
 | 
				
			||||||
 | 
							two errors made by the same template will return true when called with errors.Is()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					package adverr
 | 
				
			||||||
							
								
								
									
										104
									
								
								error.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								error.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,104 @@
 | 
				
			|||||||
 | 
					package adverr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Error is a wrapper for error with stack trace
 | 
				
			||||||
 | 
					type Error struct {
 | 
				
			||||||
 | 
						msg       string
 | 
				
			||||||
 | 
						callTrace *CallTrace
 | 
				
			||||||
 | 
						tmpl      *ErrTmpl
 | 
				
			||||||
 | 
						cause     error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// New returns a new Error with the given message
 | 
				
			||||||
 | 
					func New(msg string) *Error {
 | 
				
			||||||
 | 
						return &Error{
 | 
				
			||||||
 | 
							msg:       msg,
 | 
				
			||||||
 | 
							cause:     nil,
 | 
				
			||||||
 | 
							tmpl:      nil,
 | 
				
			||||||
 | 
							callTrace: Trace(2),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Wrap returns a new Error with the given message which is caused by cause
 | 
				
			||||||
 | 
					func Wrap(msg string, cause error) *Error {
 | 
				
			||||||
 | 
						return &Error{
 | 
				
			||||||
 | 
							msg:       msg,
 | 
				
			||||||
 | 
							cause:     cause,
 | 
				
			||||||
 | 
							callTrace: Trace(2),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func errtype(err error) string {
 | 
				
			||||||
 | 
						if e, ok := err.(*Error); ok && e.tmpl != nil {
 | 
				
			||||||
 | 
							return errtype(e.tmpl)
 | 
				
			||||||
 | 
						} else if tmpl, ok := err.(*ErrTmpl); ok {
 | 
				
			||||||
 | 
							t := reflect.TypeOf(tmpl)
 | 
				
			||||||
 | 
							for t.Kind() == reflect.Ptr {
 | 
				
			||||||
 | 
								t = t.Elem()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return t.PkgPath() + "." + tmpl.name
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t := reflect.TypeOf(err)
 | 
				
			||||||
 | 
						for t.Kind() == reflect.Ptr {
 | 
				
			||||||
 | 
							t = t.Elem()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return t.PkgPath() + "." + t.Name()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *Error) Unwrap() error {
 | 
				
			||||||
 | 
						return e.cause
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (e *Error) Error() string {
 | 
				
			||||||
 | 
						b := new(strings.Builder)
 | 
				
			||||||
 | 
						printErr(e, b)
 | 
				
			||||||
 | 
						return b.String()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Is implements the error equality function used by errors.Is()
 | 
				
			||||||
 | 
					// It returns true if the error is the same instance or is created using the same ErrTmpl
 | 
				
			||||||
 | 
					func (e *Error) Is(target error) bool {
 | 
				
			||||||
 | 
						// same error instance
 | 
				
			||||||
 | 
						if target == e {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// no template used, therefore no equality possible
 | 
				
			||||||
 | 
						if e.tmpl == nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// same template, therefore errors are equal to another
 | 
				
			||||||
 | 
						if tErr, ok := target.(*Error); ok {
 | 
				
			||||||
 | 
							return tErr.tmpl == e.tmpl
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func printErr(err error, b *strings.Builder) {
 | 
				
			||||||
 | 
						if e, ok := err.(*Error); ok {
 | 
				
			||||||
 | 
							b.WriteString(errtype(e))
 | 
				
			||||||
 | 
							b.WriteString(": ")
 | 
				
			||||||
 | 
							b.WriteString(e.msg)
 | 
				
			||||||
 | 
							b.WriteString("\n")
 | 
				
			||||||
 | 
							b.WriteString(e.callTrace.String())
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							b.WriteString(errtype(err))
 | 
				
			||||||
 | 
							b.WriteString(": ")
 | 
				
			||||||
 | 
							b.WriteString(err.Error())
 | 
				
			||||||
 | 
							b.WriteString("\n")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cause := errors.Unwrap(err)
 | 
				
			||||||
 | 
						if cause != nil {
 | 
				
			||||||
 | 
							b.WriteString("Caused by ")
 | 
				
			||||||
 | 
							printErr(cause, b)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								error_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								error_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					package adverr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestErr(t *testing.T) {
 | 
				
			||||||
 | 
						TraceCallStack = false
 | 
				
			||||||
 | 
						err := doStuff()
 | 
				
			||||||
 | 
						fmt.Println(err)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func doStuff() error {
 | 
				
			||||||
 | 
						return Wrap("wrapped error", Wrap("test error", fmt.Errorf("asd: %w", errors.New("test"))))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										35
									
								
								error_tmpl.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								error_tmpl.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					package adverr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						// ErrTmplUsedAsErr is returned from ErrTmpl.Error() because an error template should never be used as an actual error value
 | 
				
			||||||
 | 
						ErrTmplUsedAsErr = NewErrTmpl("ErrTmplUsedAsErr", "Error template used as error value: %s")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ErrTmpl is an error template to define equalities between different errors
 | 
				
			||||||
 | 
					type ErrTmpl struct {
 | 
				
			||||||
 | 
						name   string
 | 
				
			||||||
 | 
						format string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewErrTmpl returns a new error template with the given format string as a predefined error message
 | 
				
			||||||
 | 
					func NewErrTmpl(name, format string) *ErrTmpl {
 | 
				
			||||||
 | 
						return &ErrTmpl{name, format}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Error implementation just for satisfying the error interface
 | 
				
			||||||
 | 
					// Please dont use ErrTmpls as actual errors
 | 
				
			||||||
 | 
					func (t *ErrTmpl) Error() string {
 | 
				
			||||||
 | 
						return ErrTmplUsedAsErr.New(errtype(t)).Error()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// New returns a new Error in which the given values are being formatted into the format string of its template
 | 
				
			||||||
 | 
					func (t *ErrTmpl) New(args ...interface{}) *Error {
 | 
				
			||||||
 | 
						return &Error{
 | 
				
			||||||
 | 
							msg:       fmt.Sprintf(t.format, args...),
 | 
				
			||||||
 | 
							cause:     nil,
 | 
				
			||||||
 | 
							tmpl:      t,
 | 
				
			||||||
 | 
							callTrace: Trace(2),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								error_tmpl_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								error_tmpl_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					package adverr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						ErrDoStuffFailed = NewErrTmpl("ErrDoStuffFailed", "test error: %s")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestErrTmpl(t *testing.T) {
 | 
				
			||||||
 | 
						err := doTemplateStuff()
 | 
				
			||||||
 | 
						fmt.Println(err)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func doTemplateStuff() error {
 | 
				
			||||||
 | 
						return ErrDoStuffFailed.New("because of reasons")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								globals.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								globals.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					package adverr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TraceCallStack decides if any call stack traces will be gathered when creating Errors
 | 
				
			||||||
 | 
					// If your application is doing performance-heavy tasks with lots of Error creations, you may consider setting this to false
 | 
				
			||||||
 | 
					// If set to false, all CallTraces in Error will be nil
 | 
				
			||||||
 | 
					var TraceCallStack bool = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CallStackLength decides how many calls from the call stack should be gathered at most
 | 
				
			||||||
 | 
					var CallStackLength int = 100
 | 
				
			||||||
							
								
								
									
										28
									
								
								utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								utils.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					package adverr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Println prints the given err to stderr followed by a newline
 | 
				
			||||||
 | 
					func Println(err error) {
 | 
				
			||||||
 | 
						fmt.Fprintln(os.Stderr, err)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Print prints the given err to stderr
 | 
				
			||||||
 | 
					func Print(err error) {
 | 
				
			||||||
 | 
						fmt.Fprint(os.Stderr, err)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Fatalln prints the given err to stderr followed by a newline and exits immediately with the given exit code
 | 
				
			||||||
 | 
					func Fatalln(err error, exitcode int) {
 | 
				
			||||||
 | 
						fmt.Fprintln(os.Stderr, err)
 | 
				
			||||||
 | 
						os.Exit(exitcode)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Fatal prints the given err to stderr and exits immediately with the given exit code
 | 
				
			||||||
 | 
					func Fatal(err error, exitcode int) {
 | 
				
			||||||
 | 
						fmt.Fprint(os.Stderr, err)
 | 
				
			||||||
 | 
						os.Exit(exitcode)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user