#!/usr/bin/env nu # Provider Error Handling - Standardized error handling across providers # # This module consolidates error handling patterns: # - Command execution with error handling # - Error classification # - Debug vs production output # - Retry logic # Execute command with structured error handling export def provider_execute_safe [cmd: string, context: string = "command", debug_on_error: bool = true, suppress_throw: bool = false] { let result = (^$cmd | complete) {exit_code: $result.exit_code, stdout: $result.stdout, stderr: $result.stderr, success: ($result.exit_code == 0), command: $cmd, context: $context} } # Check command result and handle error export def provider_check_result [result: record, error_exit: bool = false, throw_error: bool = false] { if not $result.success { if (is-debug-enabled) { print $"Error in ($result.context):" print $"Exit code: ($result.exit_code)" if $result.stderr != "" { print $"Stderr: ($result.stderr)" } if $result.stdout != "" { print $"Stdout: ($result.stdout)" } } else { print $"Error ($result.context): ($result.exit_code)" if ($result.stdout | str contains "error") { print $result.stdout | ^grep "error" } } if $throw_error { (throw-error $"Error: ($result.context)" $result.stderr --span (metadata $result).span) } if $error_exit { exit 1 } return false } true } # Categorize error by type export def provider_classify_error [exit_code: int, stderr: string = ""] { if $exit_code == 0 { "success" } else if ($stderr | str contains "Not found") { "not_found" } else if ($stderr | str contains "Permission denied") { "permission" } else if ($stderr | str contains "Timeout") { "timeout" } else if ($stderr | str contains "Connection") { "network" } else if ($stderr | str contains "401") { "auth" } else if ($stderr | str contains "403") { "forbidden" } else if ($stderr | str contains "409") { "conflict" } else if $exit_code == 127 { "not_found" } else if $exit_code > 0 { "general_error" } else { "unknown" } } # Handle specific error types export def provider_handle_error_type [error_type: string, context: string = "", recoverable: bool = false] { match $error_type { "not_found" => { print $"Error: Resource not found ($context)"; false }, "permission" => { print $"Error: Permission denied ($context)"; false }, "timeout" => { print $"Error: Timeout ($context)"; $recoverable }, "network" => { print $"Error: Network error ($context)"; $recoverable }, "auth" => { print $"Error: Authentication failed ($context)"; false }, "forbidden" => { print $"Error: Access forbidden ($context)"; false }, "conflict" => { print $"Error: Resource conflict ($context)"; $recoverable }, _ => { print $"Error: ($error_type) in ($context)"; false } } } # Execute with retry logic export def provider_execute_with_retry [cmd: string, max_retries: int = 3, retry_delay_secs: int = 5, context: string = "command"] { mut result = (provider_execute_safe $cmd $context) mut retry_count = 0 while (not $result.success) and $retry_count < $max_retries { let error_type = (provider_classify_error $result.exit_code $result.stderr) let should_retry = (provider_handle_error_type $error_type $context true) if $should_retry { $retry_count = $retry_count + 1 let ratio = $"($retry_count)/($max_retries)" print $"Retrying... ($ratio)" sleep ($"($retry_delay_secs)sec" | into duration) $result = (provider_execute_safe $cmd $context) } else { break } } $result | insert retry_count $retry_count } # Validate response structure export def provider_validate_response [response: record, required_fields: list = []] { if ($response | is-empty) { print "Error: Empty response"; return false } if ($required_fields | is-empty) { return true } let missing = $required_fields | where {|field| not ($response | has $field)} if not ($missing | is-empty) { print $"Error: Missing required fields: ($missing | str join ', ')"; return false } true } # Extract error message from response export def provider_extract_error_message [response: record | string, error_path: string = "error"] { if ($response | describe) == "string" { $response } else if ($response | has $error_path) { $response | get $error_path | into string } else if ($response | has "message") { $response | get "message" | into string } else if ($response | has "error_message") { $response | get "error_message" | into string } else if ($response | has "error") { let err = ($response | get "error") if ($err | describe) == "string" { $err } else { ($err | into string) } } else { ($response | into string) } } # Check for common API errors in response export def provider_check_api_error [response: record | string] { let error_indicators = ["error", "Error", "ERROR", "failed", "Failed", "invalid", "not found"] let has_error = if ($response | describe) == "string" { $error_indicators | any {|ind| $response | str contains $ind} } else if ($response | is-empty) { true } else { false } let error_message = if $has_error { (provider_extract_error_message $response "error") } else { "" } {has_error: $has_error, message: $error_message} } # Suppress output on success export def provider_output_device [suppress: bool = false] { if $suppress { if $nu.os-info.name == "windows" { "NUL" } else { "/dev/null" } } else { "" } } # Format error for user display export def provider_format_error [error_type: string, context: string, message: string = "", suggest_retry: bool = false] { let header = $"Error: ($error_type) in ($context)" let body = if $message != "" { $"($message)" } else { "" } let suggestion = if $suggest_retry { "Retry may help." } else { "" } [$header, $body, $suggestion] | where {|s| $s != "" } | str join "\n" }