Documentation
¶
Overview ¶
Package clierror provides reusable error catching templates and helpers.
This file contains defer/recover templates that wrap panics and errors in the standard error structure and map them to appropriate exit codes.
Usage Examples:
Simple panic recovery:
func myFunction() {
defer clierror.RecoverPanic()
// ... code that might panic
}
Panic recovery with custom handler:
func myFunction() {
defer clierror.RecoverPanicWithHandler(func(r interface{}) error {
return clierror.NewInternal("panic occurred", fmt.Errorf("%v", r))
})
// ... code that might panic
}
Comprehensive error catching in main():
func main() {
clierror.RunSafe(run)
}
func run() error {
// Your main logic here
return nil
}
Package clierror provides utilities for CLI error handling, including commit SHA retrieval.
Package clierror provides standard error handling infrastructure for CLI entry points.
This package provides utilities for catching panics, wrapping errors with context, error categorization, and ensuring clean error propagation from CLI main functions without panics reaching the runtime.
Usage:
func main() {
clierror.Run(run)
}
func run() error {
// Your main logic here
return nil
}
Package clierror provides standard error handling infrastructure for CLI entry points.
This file contains comprehensive examples of error wrapping patterns that can be used as reference when implementing or refactoring CLI commands.
Index ¶
- Variables
- func CatchPanic(fn func()) (err error)
- func CleanupAndRecover(errPtr *error, cleanup func())
- func DeferRecover(errPtr *error)
- func GetCommitSHA() string
- func GetExitCode(err error) int
- func Must(fn func() error)
- func MustValue[T any](fn func() (T, error)) T
- func RecoverAndReturn(errPtr *error)
- func RecoverPanic()
- func RecoverPanicWithHandler(handler func(r interface{}) error)
- func Run(fn func() error)
- func RunSafe(fn func() error)
- func RunSafeWithDefault(fn func() error, defaultExitCode int)
- func RunWithExitCode(fn func() error, successCode int)
- func Safe(fn func() error) (err error)
- func WithErrorRecovery(fn func() error) func() error
- func WrapInput(message string, err error) error
- func WrapInputWithContext(message, context string, err error) error
- func WrapInternal(message string, err error) error
- func WrapNetwork(message string, err error) error
- func WrapPermission(message string, err error) error
- func WrapTransient(message string, err error) error
- func WrapUsage(message string, err error) error
- type ErrorCategory
- type ExitCodeError
- type WrappedError
- func NewInput(message string, err error) *WrappedError
- func NewInputWithContext(message, context string, err error) *WrappedError
- func NewInternal(message string, err error) *WrappedError
- func NewNetwork(message string, err error) *WrappedError
- func NewPermission(message string, err error) *WrappedError
- func NewTransient(message string, err error) *WrappedError
- func NewUsage(message string, err error) *WrappedError
- func NewWrap(category ErrorCategory, message string, err error) *WrappedError
- func NewWrapWithContext(category ErrorCategory, message, context string, err error) *WrappedError
- func NewWrapWithExitCode(category ErrorCategory, message string, exitCode int, err error) *WrappedError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var DefaultExitCodes = map[ErrorCategory]int{ CategoryUsage: 2, CategoryInput: 3, CategoryNetwork: 4, CategoryPermission: 5, CategoryInternal: 70, CategoryTransient: 75, }
DefaultExitCodes maps error categories to their default exit codes. These follow common CLI conventions: - 1: General errors - 2: Usage errors (like getopt) - 3: Input/data errors - 4: Network errors - 5: Permission errors - 70: Internal software errors (like sysexits.h) - 75: Transient/temporary errors
Functions ¶
func CatchPanic ¶
func CatchPanic(fn func()) (err error)
CatchPanic is a generic panic catching template that returns an error if a panic occurred, or nil if the function completed successfully.
This is useful for inline panic catching without defer statements.
Example:
if err := clierror.CatchPanic(func() {
// Code that might panic
mightPanic()
}); err != nil {
log.Printf("Caught panic: %v", err)
}
func CleanupAndRecover ¶
func CleanupAndRecover(errPtr *error, cleanup func())
CleanupAndRecover combines cleanup execution with panic recovery. It ensures cleanup runs even if a panic occurs, then converts panic to error.
Example:
func process(data []byte) (err error) {
file, err := os.Open("data.txt")
if err != nil {
return err
}
defer clierror.CleanupAndRecover(&err, func() {
file.Close()
})
// ... process file, potential panic here
return nil
}
func DeferRecover ¶
func DeferRecover(errPtr *error)
DeferRecover is a template function for deferred error recovery. It's useful when you need to perform cleanup and error handling together.
Example:
func process(data []byte) (err error) {
defer clierror.DeferRecover(&err)
file, err := os.Open("data.txt")
if err != nil {
return NewInputWithContext("failed to open file", "data.txt", err)
}
defer file.Close()
// ... process file
return nil
}
func GetCommitSHA ¶
func GetCommitSHA() string
GetCommitSHA retrieves the commit SHA from the following sources in order: 1. The .needle-predispatch-sha file in the repository root 2. git rev-parse HEAD command (fallback) 3. Empty string if neither method succeeds
This function is called by entry points to obtain the commit SHA that should be threaded through all intermediate function calls to error constructors.
Returns the commit SHA as a string (40-character hexadecimal) or empty string if unavailable.
func GetExitCode ¶
GetExitCode extracts the appropriate exit code from an error. For WrappedError, it uses the error's ExitCode() method. For ExitCodeError, it uses the embedded ExitCode field. For other errors, it returns 1.
func Must ¶
func Must(fn func() error)
Must executes a function and panics with an internal error if it returns an error. This is useful for initialization code that must succeed.
Example:
func init() {
clierror.Must(loadConfig())
clierror.Must(connectDatabase())
}
func loadConfig() error {
// Load configuration
return nil
}
Example ¶
// Initialization that must succeed
initialize := func() error {
return nil
}
// Use in init():
// func init() {
// clierror.Must(initialize())
// }
_ = initialize
func MustValue ¶
MustValue executes a function that returns a value and an error. It panics with an internal error if the function returns an error, otherwise returns the value.
Example:
func main() {
config := clierror.MustValue(loadConfig)
_ = config
}
func loadConfig() (*Config, error) {
cfg, err := readConfig()
if err != nil {
return nil, err
}
return cfg, nil
}
func RecoverAndReturn ¶
func RecoverAndReturn(errPtr *error)
RecoverAndReturn is a defer/recover template for functions that return errors. It catches panics, converts them to errors, and assigns them to a pointer.
This pattern is useful when you want a function to return errors instead of panicking, making error handling more explicit.
Example:
func process(data []byte) (err error) {
defer clierror.RecoverAndReturn(&err)
var result interface{}
if err := json.Unmarshal(data, &result); err != nil {
return WrapInput("JSON parse failed", err)
}
_ = result
return nil
}
Example ¶
// Converting panics to returned errors
parseJSON := func(data []byte) (err error) {
defer RecoverAndReturn(&err)
var result map[string]interface{}
if err := json.Unmarshal(data, &result); err != nil {
panic(fmt.Sprintf("JSON parse failed: %v", err))
}
return nil
}
_ = parseJSON([]byte(`{"key": "value"}`))
func RecoverPanic ¶
func RecoverPanic()
RecoverPanic is a simple defer/recover template that catches panics, logs them with stack trace, and converts them to internal errors.
This function should be used as a defer statement at the beginning of functions that might panic. It never returns normally - it panics again with a WrappedError containing the original panic value.
Example:
func process(data []byte) {
defer clierror.RecoverPanic()
var result interface{}
if err := json.Unmarshal(data, &result); err != nil {
panic(fmt.Sprintf("JSON parse failed: %v", err))
}
_ = result
}
Example ¶
Example usage tests (documentation through tests)
// Simple panic recovery in a function
processData := func(data []byte) {
defer RecoverPanic()
// Code that might panic
if len(data) == 0 {
panic("no data to process")
}
}
processData([]byte{1, 2, 3})
func RecoverPanicWithHandler ¶
func RecoverPanicWithHandler(handler func(r interface{}) error)
RecoverPanicWithHandler is a defer/recover template that catches panics and passes them to a custom handler function for conversion to errors.
The handler function receives the panic value and should return an error (typically a WrappedError). If the handler returns nil, no panic is re-raised. Otherwise, the returned error is used as the panic value.
Example:
func process(data []byte) {
defer clierror.RecoverPanicWithHandler(func(r interface{}) error {
errMsg := fmt.Sprintf("processing failed: %v", r)
return clierror.NewInput("data processing error", fmt.Errorf(errMsg))
})
// ... code that might panic
}
func Run ¶
func Run(fn func() error)
Run executes a function, catching panics and handling errors cleanly.
If fn returns an error, Run prints it to stderr and exits with the appropriate code (determined by the error type: 1 for general errors, or custom codes from ExitCodeError/WrappedError).
If fn panics, Run recovers, logs the panic with stack trace, and exits with code 1.
Run never returns - it always calls os.Exit().
func RunSafe ¶
func RunSafe(fn func() error)
RunSafe executes a function with comprehensive panic and error handling. It catches panics, handles errors, and exits with appropriate codes.
This is the main entry point template for CLI applications. It combines panic recovery with error handling and exit code mapping.
If fn panics, RunSafe logs the panic with stack trace and exits with code 70. If fn returns an error, RunSafe prints the error and exits with the appropriate code based on the error type. If fn succeeds, RunSafe exits with code 0.
Example:
func main() {
clierror.RunSafe(run)
}
func run() error {
// Your main logic here
if *inputPath == "" {
return NewUsage("missing required flag",
errors.New("--input is required"))
}
return nil
}
Example ¶
// Main entry point with panic and error handling
run := func() error {
// Your main logic here
return nil
}
// Use in main():
// func main() {
// clierror.RunSafe(run)
// }
_ = run
func RunSafeWithDefault ¶
RunSafeWithDefault executes a function with panic/error handling and a default exit code for unspecified errors.
This is similar to RunSafe but allows specifying a custom exit code for general errors (instead of the default 1).
Example:
func main() {
clierror.RunSafeWithDefault(run, 2) // Use exit code 2 for general errors
}
func RunWithExitCode ¶
RunWithExitCode executes a function, catching panics and handling errors cleanly.
If fn returns an error, RunWithExitCode prints it to stderr and exits with the appropriate code (determined by the error type: 1 for general errors, or custom codes from ExitCodeError/WrappedError).
If fn panics, RunWithExitCode recovers, logs the panic with stack trace, and exits with code 1.
If fn returns successfully, RunWithExitCode exits with the provided success code.
RunWithExitCode never returns - it always calls os.Exit().
func Safe ¶
Safe executes a function and returns any error (or panic converted to error). This ensures a function never panics - all panics are converted to errors.
Example:
if err := clierror.Safe(func() error {
// Code that might panic or return an error
return process(data)
}); err != nil {
log.Printf("Operation failed: %v", err)
}
Example ¶
// Ensure a function never panics
result := Safe(func() error {
// Code that might panic or return an error
return nil
})
_ = result
func WithErrorRecovery ¶
WithErrorRecovery is a higher-order function that wraps a function with panic recovery. The wrapped function will convert any panic to an error.
This is useful for creating panic-safe function wrappers.
Example:
var safeProcess = clierror.WithErrorRecovery(process)
err := safeProcess(data)
if err != nil {
log.Printf("Process failed: %v", err)
}
func WrapInput ¶
WrapInput is a convenience function that wraps an error with input category only if the error is non-nil. Returns nil if err is nil.
func WrapInputWithContext ¶
WrapInputWithContext is a convenience function that wraps an error with input category and context only if the error is non-nil.
func WrapInternal ¶
WrapInternal is a convenience function that wraps an error with internal category only if the error is non-nil.
func WrapNetwork ¶
WrapNetwork is a convenience function that wraps an error with network category only if the error is non-nil.
func WrapPermission ¶
WrapPermission is a convenience function that wraps an error with permission category only if the error is non-nil.
func WrapTransient ¶
WrapTransient is a convenience function that wraps an error with transient category only if the error is non-nil.
Types ¶
type ErrorCategory ¶
type ErrorCategory string
ErrorCategory represents a classification of errors for consistent exit code mapping.
const ( // CategoryUsage indicates command-line usage errors (invalid flags, missing arguments, etc.) CategoryUsage ErrorCategory = "usage" // CategoryInput indicates input validation or data errors (invalid files, malformed data, etc.) CategoryInput ErrorCategory = "input" // CategoryNetwork indicates network-related errors (connection failures, timeouts, etc.) CategoryNetwork ErrorCategory = "network" // CategoryPermission indicates permission or authorization errors CategoryPermission ErrorCategory = "permission" // CategoryInternal indicates internal errors that should not occur (bugs, panics, etc.) CategoryInternal ErrorCategory = "internal" // CategoryTransient indicates transient errors that might succeed on retry CategoryTransient ErrorCategory = "transient" )
type ExitCodeError ¶
type ExitCodeError struct {
// ExitCode is the code to exit with (defaults to 1 if 0).
ExitCode int
// Err is the underlying error.
Err error
}
ExitCodeError is an error that carries a specific exit code.
func NewExitCodeError ¶
func NewExitCodeError(code int, err error) *ExitCodeError
NewExitCodeError creates a new ExitCodeError with the given code and underlying error.
func (*ExitCodeError) Error ¶
func (e *ExitCodeError) Error() string
Error implements the error interface.
func (*ExitCodeError) Unwrap ¶
func (e *ExitCodeError) Unwrap() error
Unwrap returns the underlying error for errors.Is/As compatibility.
type WrappedError ¶
type WrappedError struct {
// Category classifies the error type for exit code mapping
Category ErrorCategory
// Message is a human-readable description of what operation failed
Message string
// Context provides additional context about the error
Context string
// Err is the underlying error (may be nil for standalone errors)
Err error
// ExitCodeOverride allows overriding the default exit code for this category
ExitCodeOverride int
}
WrappedError provides rich context wrapping for errors with categorization. It supports standard error wrapping patterns while adding structured context for better error messages and exit code mapping.
func NewInput ¶
func NewInput(message string, err error) *WrappedError
NewInput creates a new input error (exit code 3 by default). Use this for file validation, data parsing, and input errors.
func NewInputWithContext ¶
func NewInputWithContext(message, context string, err error) *WrappedError
NewInputWithContext creates a new input error with additional context. Use this when you need to specify which file or input source failed.
func NewInternal ¶
func NewInternal(message string, err error) *WrappedError
NewInternal creates a new internal error (exit code 70 by default). Use this for bugs and errors that should never occur.
func NewNetwork ¶
func NewNetwork(message string, err error) *WrappedError
NewNetwork creates a new network error (exit code 4 by default). Use this for connection failures, timeouts, and HTTP errors.
func NewPermission ¶
func NewPermission(message string, err error) *WrappedError
NewPermission creates a new permission error (exit code 5 by default). Use this for authorization and access control errors.
func NewTransient ¶
func NewTransient(message string, err error) *WrappedError
NewTransient creates a new transient error (exit code 75 by default). Use this for temporary failures that might succeed on retry.
func NewUsage ¶
func NewUsage(message string, err error) *WrappedError
NewUsage creates a new usage error (exit code 2 by default). Use this for command-line argument and flag errors.
func NewWrap ¶
func NewWrap(category ErrorCategory, message string, err error) *WrappedError
NewWrap creates a new WrappedError with the given category, message, and underlying error. This is the most common wrapping function for adding context to errors.
func NewWrapWithContext ¶
func NewWrapWithContext(category ErrorCategory, message, context string, err error) *WrappedError
NewWrapWithContext creates a new WrappedError with additional context string. Use this when you need to provide extra context about what failed.
func NewWrapWithExitCode ¶
func NewWrapWithExitCode(category ErrorCategory, message string, exitCode int, err error) *WrappedError
NewWrapWithExitCode creates a new WrappedError with a custom exit code. Use this when you need to override the default exit code for a category.
func (*WrappedError) Error ¶
func (e *WrappedError) Error() string
Error implements the error interface, providing a formatted error message.
func (*WrappedError) ExitCode ¶
func (e *WrappedError) ExitCode() int
ExitCode returns the appropriate exit code for this error. It uses ExitCodeOverride if set, otherwise looks up the default for the category.
func (*WrappedError) Unwrap ¶
func (e *WrappedError) Unwrap() error
Unwrap returns the underlying error for errors.Is/As compatibility.