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