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 ¶
- Constants
- Variables
- func Attach(err error, key string, value interface{}) error
- func Attachf(err error, format string, args ...interface{}) error
- func CatchPanic(fn func()) (err error)
- func CleanupAndRecover(errPtr *error, cleanup func())
- func DeferRecover(errPtr *error)
- func FormatErrorMessage(err error) string
- func FormatErrorMessageAt(operation string, err error) string
- func FormatErrorMessageWithExitCode(err error, exitCode int) string
- func GetCommitSHA() string
- func GetExitCode(err error) int
- func Must(fn func() error)
- func MustValue[T any](fn func() (T, error)) T
- func ParseFlags(arguments []string) error
- func RecoverAndReturn(errPtr *error)
- func RecoverPanic()
- func RecoverPanicWithHandler(handler func(r interface{}) error)
- func Run(fn func(commitSHA string) error)
- func RunSafe(fn func() error)
- func RunSafeWithDefault(fn func() error, defaultExitCode int)
- func RunWithExitCode(fn func(commitSHA string) 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 WrapNetworkWithContext(message, context 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
- func Wrapf(category ErrorCategory, format string, args ...interface{}) error
- func WrapfWithContext(category ErrorCategory, format, context string, args ...interface{}) error
- type ErrorCategory
- type ErrorLogConfig
- type ErrorLogger
- 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 NewNetworkWithContext(message, context 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 ¶
const ( // ErrorLogOutputEnv selects the caught-error destination. Supported values // are "stderr" (the default) and "file". ErrorLogOutputEnv = "COMMITGRAPH_CLI_ERROR_LOG_OUTPUT" // ErrorLogFileEnv is the append-only destination used when // ErrorLogOutputEnv is set to "file". ErrorLogFileEnv = "COMMITGRAPH_CLI_ERROR_LOG_FILE" // ErrorLogLevelEnv sets the minimum slog level. Supported values are // DEBUG, INFO, WARN, ERROR, and OFF. The default is ERROR. ErrorLogLevelEnv = "COMMITGRAPH_CLI_ERROR_LOG_LEVEL" )
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 Attach ¶
Attach adds structured context to an error without wrapping it in the error chain. This is useful for adding metadata that should be displayed with the error but shouldn't participate in error comparisons (errors.Is/As). If err is nil, returns nil.
func Attachf ¶
Attachf adds formatted context to an error. This is the formatted version of Attach for dynamic attachment construction. If err is nil, returns nil.
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 FormatErrorMessage ¶
FormatErrorMessage renders an error for a person running a CLI command. Unlike the repository's structured log formatter, this presentation keeps only the details needed to understand and correct the failure: the mapped exit code, the cause, the failed operation, validation field context, and a single suggested action.
func FormatErrorMessageAt ¶
FormatErrorMessageAt is FormatErrorMessage with an explicit operation name. A non-empty operation overrides context carried by the error, which lets a command boundary describe ordinary errors that do not provide structured metadata themselves.
func FormatErrorMessageWithExitCode ¶
FormatErrorMessageWithExitCode renders an error using the exit code the command boundary will actually return. Most callers should use FormatErrorMessage; runners use this variant when they intentionally override the fallback code for an otherwise unclassified error.
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 chain. The outermost WrappedError or ExitCodeError wins, including when standard wrappers such as fmt.Errorf with %w appear outside the classified error. Typed service-layer errors from pkg/errors are translated by their category. Errors without either an exit-aware value or a recognized service category map to the general error code 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 ParseFlags ¶
ParseFlags parses the process-wide flag set without allowing package flag to terminate the process itself. Invalid flags become usage errors so the shared runner can format the message, map exit code 2, and retain structured diagnostics. Help is returned as flag.ErrHelp and treated as success by the runners after flag has printed the command's usage text.
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 ¶
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 70 (EX_SOFTWARE), consistently with the other runner variants.
Run retrieves the commit SHA using GetCommitSHA() and passes it to fn.
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 70 (EX_SOFTWARE), consistently with the other runner variants.
If fn returns successfully, RunWithExitCode exits with the provided success code.
RunWithExitCode retrieves the commit SHA using GetCommitSHA() and passes it to fn.
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 WrapNetworkWithContext ¶
WrapNetworkWithContext is a convenience function that wraps an error with network category and context 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.
func WrapUsage ¶
WrapUsage is a convenience function that wraps an error with usage category only if the error is non-nil.
func Wrapf ¶
func Wrapf(category ErrorCategory, format string, args ...interface{}) error
Wrapf creates a new WrappedError with a formatted message using fmt.Sprintf syntax. This is the formatted version of NewWrap for dynamic message construction. Returns nil if err is nil.
func WrapfWithContext ¶
func WrapfWithContext(category ErrorCategory, format, context string, args ...interface{}) error
WrapfWithContext creates a new WrappedError with a formatted message and context. Returns nil if err is 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 ErrorLogConfig ¶
ErrorLogConfig configures the structured logger used at CLI error boundaries. Output defaults to stderr, Fallback defaults to stderr, and Level defaults to ERROR when omitted.
type ErrorLogger ¶
type ErrorLogger struct {
// contains filtered or unexported fields
}
ErrorLogger emits one JSON object per caught CLI error. Its stable top-level fields are timestamp, level, msg, entry_point, catch_site, error_type, message, exit_code, and formatted_error. stack_trace is added when an error already carries one or when a panic is recovered.
func NewErrorLogger ¶
func NewErrorLogger(config ErrorLogConfig) *ErrorLogger
NewErrorLogger creates a JSON error logger with an explicitly configured writer and minimum level. It is useful for embedding, tests, and callers that manage a file destination themselves.
func (*ErrorLogger) LogCaughtError ¶
func (l *ErrorLogger) LogCaughtError(entryPoint, catchSite string, err error, exitCode int) error
LogCaughtError logs an error returned to a CLI runner. Logging failures are returned after a best-effort structured fallback write; callers should keep propagating the original operation error.
func (*ErrorLogger) LogRecoveredPanic ¶
func (l *ErrorLogger) LogRecoveredPanic(entryPoint, catchSite string, value any, exitCode int) error
LogRecoveredPanic logs a panic recovered by a CLI runner and includes the recovery stack captured at the catch site.
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) ErrorLogFormat ¶
func (e *ExitCodeError) ErrorLogFormat() commiterrors.LogFormat
ErrorLogFormat adapts ExitCodeError to the standard logging contract while preserving its native Error() output and error chain.
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 NewNetworkWithContext ¶
func NewNetworkWithContext(message, context string, err error) *WrappedError
NewNetworkWithContext creates a new network error with additional context. Use this when you need to specify which host or endpoint failed.
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) ErrorLogFormat ¶
func (e *WrappedError) ErrorLogFormat() commiterrors.LogFormat
ErrorLogFormat exposes WrappedError's category, cause, context, and recovery to the repository-wide formatter without changing its long-standing Error() string contract.
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) MarshalJSON ¶
func (e *WrappedError) MarshalJSON() ([]byte, error)
MarshalJSON implements json.Marshaler interface for WrappedError. It serializes the error message string instead of the error interface.
func (*WrappedError) UnmarshalJSON ¶
func (e *WrappedError) UnmarshalJSON(data []byte) error
UnmarshalJSON implements json.Unmarshaler interface for WrappedError. It reconstructs the error from the serialized message string.
func (*WrappedError) Unwrap ¶
func (e *WrappedError) Unwrap() error
Unwrap returns the underlying error for errors.Is/As compatibility.