2025-08-06 16:52:37 +03:00

163 lines
3.9 KiB
Go

// Package errors provides a unified error handling strategy for the application.
// It extends the standard Go errors package with additional functionality for
// error wrapping, error types, and error logging.
package errors
import (
"errors"
"fmt"
"runtime"
"strings"
"git.influ.su/artmares/digglestool/internal/pkg/logger"
)
// Standard errors that can be used throughout the application.
var (
ErrNotFound = errors.New("not found")
ErrInvalidInput = errors.New("invalid input")
ErrInternal = errors.New("internal error")
ErrNotImplemented = errors.New("not implemented")
)
// Error represents an error with additional context.
type Error struct {
// Original is the original error.
Original error
// Message is an additional message to provide context.
Message string
// File is the file where the error occurred.
File string
// Line is the line where the error occurred.
Line int
}
// Error returns the error message.
func (e *Error) Error() string {
if e.Original == nil {
return e.Message
}
if e.Message == "" {
return e.Original.Error()
}
return fmt.Sprintf("%s: %s", e.Message, e.Original.Error())
}
// Unwrap returns the original error.
func (e *Error) Unwrap() error {
return e.Original
}
// New creates a new error with the given message.
func New(message string) error {
return &Error{
Message: message,
}
}
// Wrap wraps an error with additional context.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
_, file, line, _ := runtime.Caller(1)
file = trimFilePath(file)
return &Error{
Original: err,
Message: message,
File: file,
Line: line,
}
}
// Wrapf wraps an error with a formatted message.
// If err is nil, Wrapf returns nil.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
_, file, line, _ := runtime.Caller(1)
file = trimFilePath(file)
return &Error{
Original: err,
Message: fmt.Sprintf(format, args...),
File: file,
Line: line,
}
}
// Is reports whether any error in err's chain matches target.
func Is(err, target error) bool {
return errors.Is(err, target)
}
// As finds the first error in err's chain that matches target, and if so, sets
// target to that error value and returns true. Otherwise, it returns false.
func As(err error, target interface{}) bool {
return errors.As(err, target)
}
// LogError logs an error with the appropriate log level and returns it.
// This is useful for logging an error while still returning it up the call stack.
func LogError(err error) error {
if err == nil {
return nil
}
var e *Error
if errors.As(err, &e) {
logger.Errorf("[%s:%d] %s", e.File, e.Line, err.Error())
} else {
_, file, line, _ := runtime.Caller(1)
file = trimFilePath(file)
logger.Errorf("[%s:%d] %s", file, line, err.Error())
}
return err
}
// LogWarn logs an error as a warning and returns it.
func LogWarn(err error) error {
if err == nil {
return nil
}
var e *Error
if errors.As(err, &e) {
logger.Warnf("[%s:%d] %s", e.File, e.Line, err.Error())
} else {
_, file, line, _ := runtime.Caller(1)
file = trimFilePath(file)
logger.Warnf("[%s:%d] %s", file, line, err.Error())
}
return err
}
// LogFatal logs an error as fatal and exits the program.
func LogFatal(err error) {
if err == nil {
return
}
var e *Error
if errors.As(err, &e) {
logger.Fatalf("[%s:%d] %s", e.File, e.Line, err.Error())
} else {
_, file, line, _ := runtime.Caller(1)
file = trimFilePath(file)
logger.Fatalf("[%s:%d] %s", file, line, err.Error())
}
}
// trimFilePath trims the file path to make it more readable.
func trimFilePath(file string) string {
// Find the last occurrence of "git.influ.su/artmares/digglestool"
idx := strings.LastIndex(file, "git.influ.su\\artmares\\digglestool")
if idx >= 0 {
return file[idx+len("git.influ.su\\artmares\\digglestool"):]
}
return file
}