1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-12-23 22:11:10 +02:00
Files
imgproxy/errwrap/err_wrap.go
2025-08-03 03:31:23 +02:00

195 lines
4.7 KiB
Go

// errwrap package provides a way to wrap errors with additional context
package errwrap
import (
"fmt"
"net/http"
"runtime"
"strings"
)
const (
// defaultPublicMessage is the default public message for errors
defaultPublicMessage = "Internal error"
// defaultStatusCode is the default HTTP status code for errors
// (500 internal server error)
defaultStatusCode = http.StatusInternalServerError
)
type ErrWrap struct {
// err is the underlying error which is wrapped originally
error
// statusCode represents the HTTP status code for the error
statusCode int
// publicMessage is a message that is shown in the error reporting system
publicMessage string
// shouldReport indicates whether the error should be reported to the error reporting system
shouldReport bool
// stack is the original stack trace of the error
stack []uintptr
// messages is a list of messages associated with the error
messages []string
}
// New creates a New ErrWrap instance with the provided message
func New(message string, skipStackFrames int) *ErrWrap {
return From(fmt.Errorf("%s", message), skipStackFrames)
}
// Newf creates a new ErrWrap instance with a formatted message
func Errorf(skipStackFrames int, format string, args ...any) *ErrWrap {
return From(fmt.Errorf(format, args...), skipStackFrames)
}
// From creates a new ErrWrap instance from an existing error.
// In case underlying error is already an ErrWrap, it will start from scratch,
// except that it will keep the original error.
func From(err error, skipStackFrames int) *ErrWrap {
// Do not re-wrap an already wrapped error
e := err
o, ok := err.(*ErrWrap)
if ok {
e = o.Unwrap()
}
return &ErrWrap{
error: e,
statusCode: defaultStatusCode,
publicMessage: defaultPublicMessage,
shouldReport: true,
stack: callers(skipStackFrames),
messages: make([]string, 0),
}
}
// Clone creates a copy of the ErrWrap instance.
func (e *ErrWrap) Clone() *ErrWrap {
if e == nil {
return nil
}
clone := &ErrWrap{
error: e.error,
statusCode: e.statusCode,
publicMessage: e.publicMessage,
shouldReport: e.shouldReport,
stack: e.stack,
messages: make([]string, len(e.messages)),
}
copy(clone.messages, e.messages)
return clone
}
// Wrap wraps an existing error into an ErrWrap instance
func Wrap(err error) *ErrWrap {
if err == nil {
return nil
}
var wrapped *ErrWrap
if existing, ok := err.(*ErrWrap); ok {
wrapped = existing.Clone()
} else {
wrapped = &ErrWrap{
error: err,
statusCode: defaultStatusCode,
publicMessage: defaultPublicMessage,
shouldReport: true,
stack: callers(0),
messages: make([]string, 0),
}
}
return wrapped
}
// Wrapf wraps an existing error into an ErrWrap instance with a formatted message
func Wrapf(err error, msg string, args ...any) *ErrWrap {
if err == nil {
return nil
}
wrapped := Wrap(err)
formatted := fmt.Sprintf(msg, args...)
wrapped.messages = append(wrapped.messages, formatted)
return wrapped
}
// Error returns the error message
func (e *ErrWrap) Error() string {
if len(e.messages) > 0 {
return fmt.Sprintf("%s: %s", e.error.Error(), strings.Join(e.messages, ": "))
}
return e.error.Error()
}
// Unwrap returns the underlying error
func (e *ErrWrap) Unwrap() error {
return e.error
}
// StatusCode returns the HTTP status code
func (e *ErrWrap) StatusCode() int {
return e.statusCode
}
// PublicMessage returns the public message
func (e *ErrWrap) PublicMessage() string {
return e.publicMessage
}
// ShouldReport returns whether the error should be reported
func (e *ErrWrap) ShouldReport() bool {
return e.shouldReport
}
// WithStatusCode sets the HTTP status code and returns a new instance
func (e *ErrWrap) WithStatusCode(code int) *ErrWrap {
newErr := e.Clone()
newErr.statusCode = code
return newErr
}
// WithPublicMessage sets the public message and returns a new instance
func (e *ErrWrap) WithPublicMessage(msg string) *ErrWrap {
newErr := e.Clone()
newErr.publicMessage = msg
return newErr
}
// WithShouldReport sets whether the error should be reported and returns a new instance
func (e *ErrWrap) WithShouldReport(report bool) *ErrWrap {
// Create a copy to maintain immutability
newErr := e.Clone()
newErr.shouldReport = report
return newErr
}
// FormatStack formats the stack trace into a human-readable string
func (e *ErrWrap) FormatStack() string {
lines := make([]string, len(e.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")
}
// callers captures the stack trace
func callers(skip int) []uintptr {
stack := make([]uintptr, 10)
n := runtime.Callers(skip+2, stack)
return stack[:n]
}