mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-12-23 22:11:10 +02:00
195 lines
4.7 KiB
Go
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]
|
|
}
|