error_framework.sh

Download
bash 349 lines 9.6 KB
  1#!/usr/bin/env bash
  2set -euo pipefail
  3
  4# Error Handling Framework
  5# Demonstrates reusable error handling patterns for robust shell scripts
  6
  7# ============================================================================
  8# Color definitions for output
  9# ============================================================================
 10
 11readonly RED='\033[0;31m'
 12readonly GREEN='\033[0;32m'
 13readonly YELLOW='\033[1;33m'
 14readonly BLUE='\033[0;34m'
 15readonly CYAN='\033[0;36m'
 16readonly MAGENTA='\033[0;35m'
 17readonly NC='\033[0m' # No Color
 18
 19# ============================================================================
 20# Error Codes
 21# ============================================================================
 22
 23readonly E_SUCCESS=0
 24readonly E_GENERIC=1
 25readonly E_INVALID_ARG=2
 26readonly E_FILE_NOT_FOUND=3
 27readonly E_PERMISSION_DENIED=4
 28readonly E_NETWORK_ERROR=5
 29readonly E_TIMEOUT=6
 30readonly E_DEPENDENCY_MISSING=7
 31
 32# ============================================================================
 33# Error Handling Functions
 34# ============================================================================
 35
 36# Print error message and exit with code
 37die() {
 38    local message="$1"
 39    local exit_code="${2:-$E_GENERIC}"
 40
 41    echo -e "${RED}ERROR:${NC} $message" >&2
 42    exit "$exit_code"
 43}
 44
 45# Print warning message without exiting
 46warn() {
 47    local message="$1"
 48    echo -e "${YELLOW}WARNING:${NC} $message" >&2
 49}
 50
 51# Print info message
 52info() {
 53    local message="$1"
 54    echo -e "${CYAN}INFO:${NC} $message"
 55}
 56
 57# Print success message
 58success() {
 59    local message="$1"
 60    echo -e "${GREEN}SUCCESS:${NC} $message"
 61}
 62
 63# ============================================================================
 64# Stack Trace on Error
 65# ============================================================================
 66
 67# Print stack trace
 68print_stack_trace() {
 69    local frame=0
 70    local line_no
 71    local func_name
 72    local source_file
 73
 74    echo -e "${MAGENTA}Stack trace:${NC}" >&2
 75
 76    # Skip the first frame (this function itself)
 77    for ((frame=1; frame < ${#FUNCNAME[@]}; frame++)); do
 78        func_name="${FUNCNAME[$frame]}"
 79        source_file="${BASH_SOURCE[$frame]}"
 80        line_no="${BASH_LINENO[$((frame-1))]}"
 81
 82        echo -e "  ${CYAN}[$frame]${NC} $func_name at ${source_file}:${line_no}" >&2
 83    done
 84}
 85
 86# Error handler that shows context
 87error_handler() {
 88    local line_no="$1"
 89    local bash_lineno="$2"
 90    local func_name="${3:-main}"
 91    local command="$4"
 92    local error_code="${5:-1}"
 93
 94    echo >&2
 95    echo -e "${RED}╔════════════════════════════════════════════╗${NC}" >&2
 96    echo -e "${RED}║            ERROR OCCURRED                  ║${NC}" >&2
 97    echo -e "${RED}╚════════════════════════════════════════════╝${NC}" >&2
 98    echo >&2
 99    echo -e "${RED}Error Code:${NC} $error_code" >&2
100    echo -e "${RED}Function:${NC} $func_name" >&2
101    echo -e "${RED}Line:${NC} $bash_lineno" >&2
102    echo -e "${RED}Command:${NC} $command" >&2
103    echo >&2
104
105    print_stack_trace
106
107    echo >&2
108}
109
110# Setup ERR trap for automatic error context
111setup_error_trap() {
112    set -eE  # Inherit ERR trap in functions
113
114    # Note: BASH_COMMAND contains the command that triggered the error
115    trap 'error_handler ${LINENO} ${BASH_LINENO[0]} "${FUNCNAME[1]}" "$BASH_COMMAND" $?' ERR
116}
117
118# ============================================================================
119# Try/Catch Simulation Using Subshells
120# ============================================================================
121
122# Try to execute a command, catch errors
123# Returns: 0 if success, error code if failure
124try() {
125    local error_code=0
126
127    # Execute in subshell to isolate errors
128    (
129        set -e
130        "$@"
131    ) || error_code=$?
132
133    return $error_code
134}
135
136# Execute catch block if try failed
137catch() {
138    local error_code=$?
139
140    if [[ $error_code -ne 0 ]]; then
141        "$@" "$error_code"
142    fi
143
144    return 0
145}
146
147# ============================================================================
148# Demo Functions
149# ============================================================================
150
151# Function that simulates an error
152function_that_fails() {
153    local depth="${1:-1}"
154
155    if [[ $depth -gt 1 ]]; then
156        nested_function_call $((depth - 1))
157    else
158        info "About to trigger an error..."
159        # This will fail and trigger error handler
160        false
161    fi
162}
163
164# Nested function to show stack trace
165nested_function_call() {
166    local depth="$1"
167
168    if [[ $depth -gt 0 ]]; then
169        nested_function_call $((depth - 1))
170    else
171        # Trigger error
172        false
173    fi
174}
175
176# Demo: Basic error handling
177demo_die_and_warn() {
178    echo -e "\n${BLUE}=== Die and Warn Demo ===${NC}\n"
179
180    warn "This is a warning message - script continues"
181    info "Processing continues after warning"
182    success "This step succeeded"
183
184    echo
185    echo "Example: die() would exit the script"
186    echo "  die \"Critical error occurred\" $E_GENERIC"
187    echo
188}
189
190# Demo: Try/catch pattern
191demo_try_catch() {
192    echo -e "${BLUE}=== Try/Catch Pattern Demo ===${NC}\n"
193
194    # Example 1: Successful command
195    echo "Example 1: Successful command"
196    if try ls /tmp > /dev/null 2>&1; then
197        success "Command succeeded"
198    else
199        warn "Command failed (unexpected)"
200    fi
201
202    echo
203
204    # Example 2: Failing command
205    echo "Example 2: Failing command (caught)"
206    if try ls /nonexistent/path > /dev/null 2>&1; then
207        success "Command succeeded (unexpected)"
208    else
209        local error_code=$?
210        warn "Command failed with exit code $error_code (expected)"
211        info "Error was caught and handled gracefully"
212    fi
213
214    echo
215}
216
217# Demo: Try/catch with error handler
218demo_try_catch_with_handler() {
219    echo -e "${BLUE}=== Try/Catch with Error Handler ===${NC}\n"
220
221    # Define error handler function
222    handle_error() {
223        local error_code="$1"
224        echo -e "${RED}Caught error with code: $error_code${NC}"
225        echo "Performing recovery actions..."
226        echo -e "${GREEN}Recovery complete${NC}"
227    }
228
229    # Try a command that will fail
230    echo "Attempting risky operation..."
231    try some_nonexistent_command 2>/dev/null || catch handle_error
232
233    echo "Script continues after error handling"
234    echo
235}
236
237# Demo: Error codes
238demo_error_codes() {
239    echo -e "${BLUE}=== Error Code Demo ===${NC}\n"
240
241    echo "Defined error codes:"
242    echo "  E_SUCCESS=$E_SUCCESS (Success)"
243    echo "  E_GENERIC=$E_GENERIC (Generic error)"
244    echo "  E_INVALID_ARG=$E_INVALID_ARG (Invalid argument)"
245    echo "  E_FILE_NOT_FOUND=$E_FILE_NOT_FOUND (File not found)"
246    echo "  E_PERMISSION_DENIED=$E_PERMISSION_DENIED (Permission denied)"
247    echo "  E_NETWORK_ERROR=$E_NETWORK_ERROR (Network error)"
248    echo "  E_TIMEOUT=$E_TIMEOUT (Timeout)"
249    echo "  E_DEPENDENCY_MISSING=$E_DEPENDENCY_MISSING (Missing dependency)"
250
251    echo
252    echo "Example usage:"
253    echo '  [[ ! -f "$file" ]] && die "File not found: $file" $E_FILE_NOT_FOUND'
254    echo
255}
256
257# Demo: Input validation with error codes
258demo_input_validation() {
259    echo -e "${BLUE}=== Input Validation Demo ===${NC}\n"
260
261    validate_number() {
262        local input="$1"
263        local name="${2:-value}"
264
265        if [[ ! "$input" =~ ^[0-9]+$ ]]; then
266            die "Invalid $name: must be a number" $E_INVALID_ARG
267        fi
268
269        success "Valid number: $input"
270    }
271
272    validate_file() {
273        local filepath="$1"
274
275        if [[ ! -f "$filepath" ]]; then
276            die "File not found: $filepath" $E_FILE_NOT_FOUND
277        fi
278
279        if [[ ! -r "$filepath" ]]; then
280            die "Permission denied: $filepath" $E_PERMISSION_DENIED
281        fi
282
283        success "Valid file: $filepath"
284    }
285
286    # Test validation
287    echo "Testing number validation:"
288    if try validate_number "123" "test_number"; then
289        info "Validation passed"
290    else
291        warn "Validation failed (expected)"
292    fi
293
294    echo
295    echo "Testing with invalid number:"
296    if try validate_number "abc" "test_number"; then
297        warn "Validation passed (unexpected)"
298    else
299        info "Validation failed as expected"
300    fi
301
302    echo
303}
304
305# Demo: Stack trace (disabled by default to avoid script exit)
306demo_stack_trace() {
307    echo -e "${BLUE}=== Stack Trace Demo ===${NC}\n"
308
309    echo "To see a stack trace, uncomment the following line in the script:"
310    echo "  # function_that_fails 3"
311    echo
312    echo "This would call a nested function that fails,"
313    echo "triggering the error handler which prints:"
314    echo "  - Error code"
315    echo "  - Function name"
316    echo "  - Line number"
317    echo "  - Failed command"
318    echo "  - Full stack trace"
319    echo
320
321    # Uncomment to actually trigger error (will exit script):
322    # function_that_fails 3
323}
324
325# ============================================================================
326# Main Execution
327# ============================================================================
328
329main() {
330    echo -e "${BLUE}╔════════════════════════════════════════════╗${NC}"
331    echo -e "${BLUE}║       Error Handling Framework Demo       ║${NC}"
332    echo -e "${BLUE}╚════════════════════════════════════════════╝${NC}"
333
334    # Setup error trap (commented out to avoid script exit during demos)
335    # Uncomment to enable automatic error context on failures:
336    # setup_error_trap
337
338    demo_die_and_warn
339    demo_try_catch
340    demo_try_catch_with_handler
341    demo_error_codes
342    demo_input_validation
343    demo_stack_trace
344
345    echo -e "${GREEN}=== All Error Handling Demos Complete ===${NC}\n"
346}
347
348main "$@"