|
| 1 | +// Package errors provides structured, contextual error handling for Go applications. |
| 2 | +// |
| 3 | +// # Overview |
| 4 | +// |
| 5 | +// This package offers a comprehensive error handling system designed for modern Go applications. |
| 6 | +// It provides structured error types with rich metadata, stack traces, user-friendly messages, |
| 7 | +// and JSON serialization capabilities. Built for microservices and API development, it enables |
| 8 | +// consistent error handling across your entire application stack. |
| 9 | +// |
| 10 | +// # Key Features |
| 11 | +// |
| 12 | +// • Structured Error Types: Errors include codes, messages, context, timestamps, and severity levels |
| 13 | +// • Stack Trace Support: Optional lightweight stack traces for debugging |
| 14 | +// • User-Friendly Messages: Separate technical and user-facing error messages |
| 15 | +// • JSON Serialization: Built-in JSON marshaling for API responses and logging |
| 16 | +// • Retry Logic: Built-in support for retryable errors |
| 17 | +// • Interface-Based: Type-safe error handling through well-defined interfaces |
| 18 | +// • Zero Dependencies: Uses only Go standard library |
| 19 | +// • High Performance: Minimal overhead with efficient memory usage |
| 20 | +// |
| 21 | +// # Quick Start |
| 22 | +// |
| 23 | +// Create a new error with a custom code: |
| 24 | +// |
| 25 | +// const ErrCodeValidation = "VALIDATION_ERROR" |
| 26 | +// err := errors.New(ErrCodeValidation, "Username is required") |
| 27 | +// |
| 28 | +// Add user-friendly message and context: |
| 29 | +// |
| 30 | +// err = err.WithUserMessage("Please enter a username"). |
| 31 | +// WithContext("field", "username"). |
| 32 | +// WithSeverity("warning") |
| 33 | +// |
| 34 | +// Wrap existing errors with additional context: |
| 35 | +// |
| 36 | +// if err := someOperation(); err != nil { |
| 37 | +// return errors.Wrap(err, ErrCodeOperation, "Failed to process request") |
| 38 | +// } |
| 39 | +// |
| 40 | +// Check error codes programmatically: |
| 41 | +// |
| 42 | +// if errors.HasCode(err, ErrCodeValidation) { |
| 43 | +// // Handle validation error |
| 44 | +// } |
| 45 | +// |
| 46 | +// # JSON Serialization |
| 47 | +// |
| 48 | +// Errors automatically serialize to JSON with all metadata: |
| 49 | +// |
| 50 | +// jsonData, _ := json.Marshal(err) |
| 51 | +// // Output: {"code":"VALIDATION_ERROR","message":"Username is required",...} |
| 52 | +// |
| 53 | +// # Interface-Based Error Handling |
| 54 | +// |
| 55 | +// Use interfaces for type-safe error handling: |
| 56 | +// |
| 57 | +// var coder errors.ErrorCoder = err |
| 58 | +// code := coder.ErrorCode() |
| 59 | +// |
| 60 | +// var retry errors.Retryable = err |
| 61 | +// if retry.IsRetryable() { |
| 62 | +// // Implement retry logic |
| 63 | +// } |
| 64 | +// |
| 65 | +// var um errors.UserMessager = err |
| 66 | +// userMsg := um.UserMessage() |
| 67 | +// |
| 68 | +// # Best Practices |
| 69 | +// |
| 70 | +// 1. Define error codes as constants in your application |
| 71 | +// 2. Use WithUserMessage() for errors that will be displayed to users |
| 72 | +// 3. Use WithContext() to add debugging information |
| 73 | +// 4. Use Wrap() to add context to errors from lower-level functions |
| 74 | +// 5. Use AsRetryable() for transient errors that can be retried |
| 75 | +// 6. Use different severity levels for different types of errors |
| 76 | +// |
| 77 | +// # Error Severity Levels |
| 78 | +// |
| 79 | +// • "error": Standard application errors (default) |
| 80 | +// • "warning": Non-critical issues that should be noted |
| 81 | +// • "info": Informational messages |
| 82 | +// • "critical": Severe errors that require immediate attention |
| 83 | +// |
| 84 | +// # Integration Examples |
| 85 | +// |
| 86 | +// REST API Handler: |
| 87 | +// |
| 88 | +// func handleRequest(w http.ResponseWriter, r *http.Request) { |
| 89 | +// err := processRequest(r) |
| 90 | +// if err != nil { |
| 91 | +// apiErr, ok := err.(*errors.Error) |
| 92 | +// if ok { |
| 93 | +// http.Error(w, apiErr.UserMessage(), getStatusCode(apiErr)) |
| 94 | +// log.Error("API Error", "error", apiErr.Error(), "code", apiErr.ErrorCode()) |
| 95 | +// } else { |
| 96 | +// http.Error(w, "Internal server error", 500) |
| 97 | +// } |
| 98 | +// } |
| 99 | +// } |
| 100 | +// |
| 101 | +// Database Operations: |
| 102 | +// |
| 103 | +// func saveUser(user *User) error { |
| 104 | +// if err := db.Save(user); err != nil { |
| 105 | +// return errors.Wrap(err, ErrCodeDatabase, "Failed to save user"). |
| 106 | +// WithContext("user_id", user.ID). |
| 107 | +// WithUserMessage("Unable to save your information. Please try again.") |
| 108 | +// } |
| 109 | +// return nil |
| 110 | +// } |
| 111 | +// |
| 112 | +// Validation: |
| 113 | +// |
| 114 | +// func validateUser(user *User) error { |
| 115 | +// if user.Email == "" { |
| 116 | +// return errors.NewWithField(ErrCodeValidation, "Email is required", "email", user.Email). |
| 117 | +// WithUserMessage("Please provide a valid email address") |
| 118 | +// } |
| 119 | +// return nil |
| 120 | +// } |
| 121 | +// |
| 122 | +// # Testing |
| 123 | +// |
| 124 | +// Use table-driven tests for error scenarios: |
| 125 | +// |
| 126 | +// func TestValidation(t *testing.T) { |
| 127 | +// tests := []struct { |
| 128 | +// name string |
| 129 | +// input string |
| 130 | +// wantErr bool |
| 131 | +// wantCode errors.ErrorCode |
| 132 | +// }{ |
| 133 | +// {"valid email", "user@example.com", false, ""}, |
| 134 | +// {"empty email", "", true, ErrCodeValidation}, |
| 135 | +// } |
| 136 | +// |
| 137 | +// for _, tt := range tests { |
| 138 | +// t.Run(tt.name, func(t *testing.T) { |
| 139 | +// err := validateEmail(tt.input) |
| 140 | +// if tt.wantErr { |
| 141 | +// assert.True(t, errors.HasCode(err, tt.wantCode)) |
| 142 | +// } else { |
| 143 | +// assert.NoError(t, err) |
| 144 | +// } |
| 145 | +// }) |
| 146 | +// } |
| 147 | +// } |
| 148 | +// |
| 149 | +// # Performance Considerations |
| 150 | +// |
| 151 | +// • Stack traces are only captured when using Wrap() or explicitly requested |
| 152 | +// • JSON marshaling is optimized for common use cases |
| 153 | +// • Memory usage is minimal with efficient struct layout |
| 154 | +// • No reflection is used in hot paths |
| 155 | +// |
| 156 | +// # Migration from Standard Errors |
| 157 | +// |
| 158 | +// Replace standard error creation: |
| 159 | +// |
| 160 | +// // Before |
| 161 | +// return errors.New("validation failed") |
| 162 | +// |
| 163 | +// // After |
| 164 | +// return errors.New(ErrCodeValidation, "validation failed") |
| 165 | +// |
| 166 | +// Replace error wrapping: |
| 167 | +// |
| 168 | +// // Before |
| 169 | +// return fmt.Errorf("operation failed: %w", err) |
| 170 | +// |
| 171 | +// // After |
| 172 | +// return errors.Wrap(err, ErrCodeOperation, "operation failed") |
| 173 | +// |
| 174 | +// Copyright (c) 2025 AGILira |
| 175 | +// Series: an AGLIra library |
| 176 | +// SPDX-License-Identifier: MPL-2.0 |
| 177 | +package errors |
0 commit comments