functions/ErrorHandling.ps1
|
# Custom Exception Types and Error Handling for EmojiTools Module <# .SYNOPSIS Centralized error handling and custom exception types for EmojiTools. .DESCRIPTION Provides typed exceptions and standardized error handling patterns to improve error diagnostics and ensure consistent error messaging across the module. #> # Define custom exception types class EmojiToolsException : System.Exception { [string]$ErrorCode [hashtable]$Details EmojiToolsException([string]$message) : base($message) { $this.ErrorCode = "EMOJI_GENERAL_ERROR" $this.Details = @{} } EmojiToolsException([string]$message, [string]$errorCode) : base($message) { $this.ErrorCode = $errorCode $this.Details = @{} } EmojiToolsException([string]$message, [string]$errorCode, [hashtable]$details) : base($message) { $this.ErrorCode = $errorCode $this.Details = $details } } class DataNotFoundException : EmojiToolsException { DataNotFoundException([string]$message) : base($message, "EMOJI_DATA_NOT_FOUND") {} DataNotFoundException([string]$message, [hashtable]$details) : base($message, "EMOJI_DATA_NOT_FOUND", $details) {} } class CollectionNotFoundException : EmojiToolsException { CollectionNotFoundException([string]$collectionName) : base( "Collection '$collectionName' not found. Use Get-EmojiCollection to see available collections.", "EMOJI_COLLECTION_NOT_FOUND", @{ CollectionName = $collectionName } ) {} } class SourceNotFoundException : EmojiToolsException { SourceNotFoundException([string]$sourceName) : base( "Source '$sourceName' not found. Use Get-EmojiSource to list available sources.", "EMOJI_SOURCE_NOT_FOUND", @{ SourceName = $sourceName } ) {} } class SecurityValidationException : EmojiToolsException { SecurityValidationException([string]$message) : base($message, "EMOJI_SECURITY_VALIDATION_FAILED") {} SecurityValidationException([string]$message, [hashtable]$details) : base($message, "EMOJI_SECURITY_VALIDATION_FAILED", $details) {} } class NetworkException : EmojiToolsException { NetworkException([string]$message) : base($message, "EMOJI_NETWORK_ERROR") {} NetworkException([string]$message, [hashtable]$details) : base($message, "EMOJI_NETWORK_ERROR", $details) {} } class DataValidationException : EmojiToolsException { DataValidationException([string]$message) : base($message, "EMOJI_DATA_VALIDATION_FAILED") {} DataValidationException([string]$message, [hashtable]$details) : base($message, "EMOJI_DATA_VALIDATION_FAILED", $details) {} } function Write-EmojiError { <# .SYNOPSIS Centralized error handling function with consistent formatting and logging. .PARAMETER Exception The exception object to handle (can be custom EmojiToolsException or standard Exception) .PARAMETER Message Error message (used if Exception not provided) .PARAMETER Category PowerShell error category .PARAMETER ErrorCode Custom error code for tracking .PARAMETER Sanitize Whether to sanitize sensitive information from error messages .PARAMETER TargetObject The object that caused the error .EXAMPLE Write-EmojiError -Message "Failed to load data" -Category ResourceUnavailable .EXAMPLE try { ... } catch { Write-EmojiError -Exception $_.Exception } #> [CmdletBinding()] param( [Parameter(Mandatory = $false)] [System.Exception]$Exception, [Parameter(Mandatory = $false)] [string]$Message, [Parameter(Mandatory = $false)] [System.Management.Automation.ErrorCategory]$Category = [System.Management.Automation.ErrorCategory]::NotSpecified, [Parameter(Mandatory = $false)] [string]$ErrorCode, [Parameter(Mandatory = $false)] [switch]$Sanitize, [Parameter(Mandatory = $false)] [object]$TargetObject ) # Determine message and error code $finalMessage = $Message $finalErrorCode = $ErrorCode if ($Exception) { if ($Exception -is [EmojiToolsException]) { $finalMessage = $Exception.Message $finalErrorCode = $Exception.ErrorCode # Add details to message if available if ($Exception.Details.Count -gt 0) { $detailsStr = ($Exception.Details.GetEnumerator() | ForEach-Object { "$($_.Key): $($_.Value)" }) -join ", " Write-Verbose "Error details: $detailsStr" } } else { $finalMessage = if ($Message) { $Message } else { $Exception.Message } } } # Sanitize if requested or if dealing with security errors if ($Sanitize -or $Category -eq 'SecurityError') { $finalMessage = Clear-SensitiveInformation -Message $finalMessage } # Build error record $errorParams = @{ Message = $finalMessage Category = $Category } if ($Exception) { $errorParams['Exception'] = $Exception } if ($TargetObject) { $errorParams['TargetObject'] = $TargetObject } if ($finalErrorCode) { $errorParams['ErrorId'] = $finalErrorCode } # Log to verbose stream for debugging Write-Verbose "ERROR [$finalErrorCode]: $finalMessage" # Write error Write-Error @errorParams } function Write-EmojiWarning { <# .SYNOPSIS Centralized warning handler with consistent formatting. .PARAMETER Message Warning message .PARAMETER WarningCode Optional warning code for tracking .PARAMETER Sanitize Whether to sanitize sensitive information .EXAMPLE Write-EmojiWarning -Message "Dataset is outdated" -WarningCode "DATA_OUTDATED" #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Message, [Parameter(Mandatory = $false)] [string]$WarningCode, [Parameter(Mandatory = $false)] [switch]$Sanitize ) $finalMessage = if ($Sanitize) { Clear-SensitiveInformation -Message $Message } else { $Message } if ($WarningCode) { Write-Verbose "WARNING [$WarningCode]: $finalMessage" } Write-Warning $finalMessage } function Clear-SensitiveInformation { <# .SYNOPSIS Internal helper to clear sensitive information from error messages. .PARAMETER Message The message to sanitize .OUTPUTS Sanitized message string #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory = $true)] [string]$Message ) $sanitized = $Message # Remove full file paths, keep only filename $sanitized = $sanitized -replace '[A-Za-z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*([^\\/:*?"<>|\r\n]+)', '$1' # Remove potential API keys (long alphanumeric strings) $sanitized = $sanitized -replace '\b[A-Za-z0-9_-]{32,}\b', '[REDACTED]' # Remove potential tokens $sanitized = $sanitized -replace '(?i)(token|key|secret|password)\s*[:=]\s*[^\s,;]+', '$1: [REDACTED]' # Sanitize URLs but keep domain $sanitized = $sanitized -replace 'https?://([^/\s]+)[^\s]*', 'https://$1/[...]' return $sanitized } function Invoke-SafeOperation { <# .SYNOPSIS Wraps operations with standardized error handling and retry logic. .PARAMETER ScriptBlock The operation to execute .PARAMETER ErrorMessage Custom error message if operation fails .PARAMETER ErrorCategory PowerShell error category .PARAMETER RetryCount Number of retry attempts (default: 0) .PARAMETER RetryDelaySeconds Delay between retries in seconds .PARAMETER SuppressErrors If specified, errors are written to verbose instead of error stream .EXAMPLE Invoke-SafeOperation -ScriptBlock { Import-Csv $path } -ErrorMessage "Failed to load data" .EXAMPLE Invoke-SafeOperation -ScriptBlock { Invoke-WebRequest $url } -RetryCount 3 -RetryDelaySeconds 2 #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [scriptblock]$ScriptBlock, [Parameter(Mandatory = $false)] [string]$ErrorMessage = "Operation failed", [Parameter(Mandatory = $false)] [System.Management.Automation.ErrorCategory]$ErrorCategory = [System.Management.Automation.ErrorCategory]::NotSpecified, [Parameter(Mandatory = $false)] [ValidateRange(0, 10)] [int]$RetryCount = 0, [Parameter(Mandatory = $false)] [ValidateRange(0, 60)] [int]$RetryDelaySeconds = 1, [Parameter(Mandatory = $false)] [switch]$SuppressErrors ) $attempt = 0 $lastError = $null while ($attempt -le $RetryCount) { try { $attempt++ if ($attempt -gt 1) { Write-Verbose "Retry attempt $attempt of $($RetryCount + 1)" } $result = & $ScriptBlock return $result } catch { $lastError = $_ if ($attempt -le $RetryCount) { Write-Verbose "Attempt $attempt failed: $($_.Exception.Message). Retrying in $RetryDelaySeconds seconds..." Start-Sleep -Seconds $RetryDelaySeconds } } } # All attempts failed if ($SuppressErrors) { Write-Verbose "$ErrorMessage : $($lastError.Exception.Message)" return $null } else { Write-EmojiError -Exception $lastError.Exception -Message $ErrorMessage -Category $ErrorCategory return $null } } # Functions loaded via dot-sourcing in main module |