initial commit
This commit is contained in:
commit
4e5775fcfa
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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user