// 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 }