Revised errors for better error reporting

This commit is contained in:
DarthSim
2025-02-17 22:11:40 +03:00
parent 204cfa3648
commit 528ece8da1
40 changed files with 844 additions and 434 deletions

View File

@@ -2,83 +2,147 @@ package ierrors
import (
"fmt"
"net/http"
"runtime"
"strings"
)
type Option func(*Error)
type Error struct {
StatusCode int
Message string
PublicMessage string
Unexpected bool
err error
prefix string
statusCode int
publicMessage string
shouldReport bool
stack []uintptr
}
func (e *Error) Error() string {
return e.Message
}
func (e *Error) FormatStack() string {
if e.stack == nil {
return ""
if len(e.prefix) > 0 {
return fmt.Sprintf("%s: %s", e.prefix, e.err.Error())
}
return formatStack(e.stack)
return e.err.Error()
}
func (e *Error) Unwrap() error {
return e.err
}
func (e *Error) Cause() error {
return e.err
}
func (e *Error) StatusCode() int {
if e.statusCode <= 0 {
return http.StatusInternalServerError
}
return e.statusCode
}
func (e *Error) PublicMessage() string {
if len(e.publicMessage) == 0 {
return "Internal error"
}
return e.publicMessage
}
func (e *Error) ShouldReport() bool {
return e.shouldReport
}
func (e *Error) StackTrace() []uintptr {
return e.stack
}
func New(status int, msg string, pub string) *Error {
return &Error{
StatusCode: status,
Message: msg,
PublicMessage: pub,
}
func (e *Error) Callers() []uintptr {
return e.stack
}
func NewUnexpected(msg string, skip int) *Error {
return &Error{
StatusCode: 500,
Message: msg,
PublicMessage: "Internal error",
Unexpected: true,
func (e *Error) FormatStackLines() []string {
lines := make([]string, len(e.stack))
stack: callers(skip + 3),
}
}
func Wrap(err error, skip int) *Error {
if ierr, ok := err.(*Error); ok {
return ierr
}
return NewUnexpected(err.Error(), skip+1)
}
func WrapWithPrefix(err error, skip int, prefix string) *Error {
if ierr, ok := err.(*Error); ok {
newErr := *ierr
newErr.Message = fmt.Sprintf("%s: %s", prefix, ierr.Message)
return &newErr
}
return NewUnexpected(fmt.Sprintf("%s: %s", prefix, err), skip+1)
}
func callers(skip int) []uintptr {
stack := make([]uintptr, 10)
n := runtime.Callers(skip, stack)
return stack[:n]
}
func formatStack(stack []uintptr) string {
lines := make([]string, len(stack))
for i, pc := range stack {
for i, pc := range e.stack {
f := runtime.FuncForPC(pc)
file, line := f.FileLine(pc)
lines[i] = fmt.Sprintf("%s:%d %s", file, line, f.Name())
}
return strings.Join(lines, "\n")
return lines
}
func (e *Error) FormatStack() string {
return strings.Join(e.FormatStackLines(), "\n")
}
func Wrap(err error, stackSkip int, opts ...Option) *Error {
if err == nil {
return nil
}
var e *Error
if ierr, ok := err.(*Error); ok {
// if we have some options, we need to copy the error to not modify the original one
if len(opts) > 0 {
ecopy := *ierr
e = &ecopy
} else {
return ierr
}
} else {
e = &Error{
err: err,
shouldReport: true,
}
}
for _, opt := range opts {
opt(e)
}
if len(e.stack) == 0 {
e.stack = callers(stackSkip + 1)
}
return e
}
func WithStatusCode(code int) Option {
return func(e *Error) {
e.statusCode = code
}
}
func WithPublicMessage(msg string) Option {
return func(e *Error) {
e.publicMessage = msg
}
}
func WithPrefix(prefix string) Option {
return func(e *Error) {
if len(e.prefix) > 0 {
e.prefix = fmt.Sprintf("%s: %s", prefix, e.prefix)
} else {
e.prefix = prefix
}
}
}
func WithShouldReport(report bool) Option {
return func(e *Error) {
e.shouldReport = report
}
}
func callers(skip int) []uintptr {
stack := make([]uintptr, 10)
n := runtime.Callers(skip+2, stack)
return stack[:n]
}