commit 8c07c8afb0c4f707f8c9c07bf256e3137c9cc6b9 Author: Magnus Ă…hall Date: Sat Apr 11 09:15:13 2026 +0200 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..be3f38a --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +```go +package main + +import ( + // External + werr "git.gibonuddevalla.se/go/wrappederror" + + // Standard + "encoding/json" + "errors" + "fmt" + "os" +) + +func errorHandler(err werr.Error) { + // For example print or log error to file + fmt.Printf("\x1b[31;1m%s\x1b[0m\n", err) + fmt.Printf("\x1b[33;1m%s\x1b[0m\n", err.NoTrace()) + + j, _ := json.MarshalIndent(err, "", " ") + fmt.Printf("%s\n\n", j) +} + +func main() { + // Make file paths relative to this file. + werr.Init() + + // Handler to call when using Log(). + werr.SetLogCallback(errorHandler) + + // Wrap an existing error. + err := errors.New("foobar 1") + err1 := werr.Wrap(err) + + // Create a new error with extra information. + err2 := werr.New("foobar 2").WithCode("FOO-100").WithData(137) + + // The os error contains more information. + _, errOS := os.ReadFile("/tmp/does_not_exist") + werr.Wrap(errOS).Log() + + // Log the previously wrapped errors. + werr.Wrap(err1).Log() + werr.Wrap(err2).Log() +} +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b53e43f --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.ahall.se/go/wrappederror + +go 1.24.0 diff --git a/pkg.go b/pkg.go new file mode 100644 index 0000000..018ee7d --- /dev/null +++ b/pkg.go @@ -0,0 +1,173 @@ +/* +wrappederror provides traceable errors: + + package main + + import ( + // External + werr "git.gibonuddevalla.se/go/wrappederror" + + // Standard + "encoding/json" + "errors" + "fmt" + "os" + ) + + func errorHandler(err werr.Error) { + // For example print or log error to file + fmt.Printf("\x1b[31;1m%s\x1b[0m\n", err) + fmt.Printf("\x1b[33;1m%s\x1b[0m\n", err.NoTrace()) + + j, _ := json.MarshalIndent(err, "", " ") + fmt.Printf("%s\n\n", j) + } + + func main() { + // Make file paths relative to this file. + werr.Init() + + // Handler to call when using Log(). + werr.SetLogCallback(errorHandler) + + // Wrap an existing error. + err := errors.New("foobar 1") + err1 := werr.Wrap(err) + + // Create a new error with extra information. + err2 := werr.New("foobar 2").WithCode("FOO-100").WithData(137) + + // The os error contains more information. + _, errOS := os.ReadFile("/tmp/does_not_exist") + werr.Wrap(errOS).Log() + + // Log the previously wrapped errors. + werr.Wrap(err1).Log() + werr.Wrap(err2).Log() + } +*/ +package WrappedError + +import ( + // Standard + "fmt" + "path" + "regexp" + "runtime" +) + +type Error struct { + Wrapped error + ErrStr string // wrapped error isn't necessarily json encodable + Code string + File string + Line int + Data any +} + +type CodableError interface { + error + WithCode(string) CodableError + WithData(any) CodableError + Log() CodableError +} + +type LogCallback func(Error) + +var ( + logCallback LogCallback + baseDirLength int +) + +// Init only works if called from the main package and sets the length of code base path +// to later be removed in file paths to receive relative paths. +func Init() { + _, file, _, _ := runtime.Caller(1) + dirBase := path.Dir(file) + baseDirLength = len(dirBase) +} + +// SetLogCallback gives a possibility to automatically run code to handle any errors. +func SetLogCallback(cbk LogCallback) { + logCallback = cbk +} + +func callback(wrapped Error) { + if logCallback != nil { + logCallback(wrapped) + } +} + +// Error implements the error inteface and adds filename and line to the error. +func (wrapped Error) Error() string { + var code string + if wrapped.Code != "" { + code = wrapped.Code + ":" + } + return fmt.Sprintf( + "[%s%s:%d] %s", + code, + wrapped.File, + wrapped.Line, + wrapped.Wrapped.Error(), + ) +} + +func create(err error, data interface{}) *Error { + if err == nil { + return nil + } + + _, file, line, _ := runtime.Caller(2) + file = file[baseDirLength+1:] + wrapped := Error{ + Wrapped: err, + ErrStr: err.Error(), + File: file, + Line: line, + Data: data, + } + + return &wrapped +} + +// Wrap wraps an existing error with file and line. +func Wrap(err error) *Error { + return create(err, nil) +} + +// New creates a new wrapped error with file and line. +func New(msg string, params ...any) *Error { + err := fmt.Errorf(msg, params...) + wrapped := create(err, nil) + return wrapped +} + +// WithCode associates a string code with the error. +func (e *Error) WithCode(code string) CodableError { + e.Code = code + return e +} + +// WithData associates any data with the error to make troubleshooting easier. +func (e *Error) WithData(data any) CodableError { + e.Data = data + return e +} + +// Log calls the log callback. +func (e *Error) Log() CodableError { + callback(*e) + return e +} + +// NoTrace returns the Error() string without the trace. +func (e *Error) NoTrace() string { + rxp := regexp.MustCompile(`^(\[[^\]]+\.go:\d+\]\s*)*`) + return string( + rxp.ReplaceAll( + []byte(e.Error()), + []byte(""), + ), + ) +}