# NimPak Error Handling # # Comprehensive error handling utilities for the NimPak system. # Provides formatted error messages, recovery suggestions, and error chaining. # Task 37: Implement comprehensive error handling. import std/[strformat, strutils, times, tables, terminal] import ./types # ############################################################################ # Error Formatting # ############################################################################ proc errorCodeColor(code: ErrorCode): ForegroundColor = ## Get color for error code based on severity case code of PermissionDenied, ElevationRequired, ReadOnlyViolation, AculViolation, PolicyViolation, SignatureInvalid, TrustViolation: fgRed of PackageNotFound, DependencyConflict, ChecksumMismatch, InvalidMetadata, PackageCorrupted, VersionMismatch: fgYellow of NetworkError, DownloadFailed, RepositoryUnavailable, TimeoutError: fgMagenta of BuildFailed, CompilationError, MissingDependency: fgCyan else: fgWhite proc formatError*(err: NimPakError): string = ## Format a NimPakError into a human-readable string with context and suggestions result = fmt"Error [{err.code}]: {err.msg}" if err.context.len > 0: result.add fmt"\n Context: {err.context}" if err.suggestions.len > 0: result.add "\n Suggestions:" for suggestion in err.suggestions: result.add fmt"\n • {suggestion}" proc formatErrorColored*(err: NimPakError, useColor: bool = true): string = ## Format error with ANSI colors for terminal output if not useColor: return formatError(err) let color = errorCodeColor(err.code) result = "\e[" & $ord(color) & "mError [" & $err.code & "]\e[0m: " & err.msg if err.context.len > 0: result.add fmt"\n \e[90mContext:\e[0m {err.context}" if err.suggestions.len > 0: result.add "\n \e[32mSuggestions:\e[0m" for suggestion in err.suggestions: result.add fmt"\n \e[32m•\e[0m {suggestion}" # ############################################################################ # Error Factory Functions # ############################################################################ proc newNimPakError*(code: ErrorCode, message: string, context: string = "", suggestions: seq[string] = @[]): NimPakError = ## Create a new NimPakError with the specified details result = NimPakError( code: code, msg: message, context: context, suggestions: suggestions ) # --- Package Errors --- proc packageNotFoundError*(packageName: string): NimPakError = ## Create a standardized package not found error newNimPakError( PackageNotFound, fmt"Package '{packageName}' not found", "The requested package could not be located in any configured repository", @[ "Check the package name for typos", "Verify that the package repository is accessible", "Try updating the package index with 'nip update'" ] ) proc dependencyConflictError*(pkg1, pkg2, dep: string): NimPakError = newNimPakError( DependencyConflict, fmt"Dependency conflict between '{pkg1}' and '{pkg2}' for '{dep}'", "Multiple packages require incompatible versions of the same dependency", @[ "Try updating both packages to their latest versions", "Check if one package can be downgraded", "Consider using 'nip resolve --strategy=best-effort'" ] ) proc checksumMismatchError*(filePath: string, expected: string, actual: string): NimPakError = newNimPakError( ChecksumMismatch, fmt"Checksum verification failed for '{filePath}'", fmt"Expected: {expected[0..min(15, expected.len-1)]}..., Got: {actual[0..min(15, actual.len-1)]}...", @[ "The file may have been corrupted during download", "Try re-downloading the package", "Verify the integrity of your storage device" ] ) proc packageCorruptedError*(packagePath: string): NimPakError = newNimPakError( PackageCorrupted, fmt"Package at '{packagePath}' appears to be corrupted", "The package structure or manifest is invalid", @[ "Re-download the package from the source", "Verify the package signature if available", "Check for disk errors with 'fsck' or equivalent" ] ) proc invalidMetadataError*(field: string, value: string, expected: string): NimPakError = newNimPakError( InvalidMetadata, fmt"Invalid metadata: field '{field}' has invalid value", fmt"Got: '{value}', Expected: {expected}", @[ "Check the package manifest for syntax errors", "Verify the package was created with a compatible version" ] ) # --- Permission Errors --- proc permissionDeniedError*(path: string, operation: string): NimPakError = newNimPakError( PermissionDenied, fmt"Permission denied: cannot {operation} '{path}'", "The current user lacks the required permissions", @[ "Try running with elevated privileges (sudo)", "Check file/directory ownership and permissions", "Verify SELinux/AppArmor policies if applicable" ] ) proc elevationRequiredError*(operation: string): NimPakError = newNimPakError( ElevationRequired, fmt"Elevated privileges required for '{operation}'", "This operation modifies system directories", @[ "Run with 'sudo nip ...'", "Configure polkit rules for non-interactive elevation", "Use --user flag to install to user directory instead" ] ) proc readOnlyViolationError*(path: string): NimPakError = newNimPakError( ReadOnlyViolation, fmt"Cannot write to read-only path: '{path}'", "The CAS or package directory is protected", @[ "Use proper elevation to temporarily unlock", "Check if nip-protect is active", "Verify mount options for the filesystem" ] ) # --- Network Errors --- proc networkError*(url: string, details: string): NimPakError = newNimPakError( NetworkError, fmt"Network error accessing '{url}'", details, @[ "Check your internet connection", "Verify the repository URL is correct", "Try using a different mirror" ] ) proc downloadFailedError*(url: string, httpCode: int = 0): NimPakError = let context = if httpCode > 0: fmt"HTTP status: {httpCode}" else: "Connection failed" newNimPakError( DownloadFailed, fmt"Failed to download from '{url}'", context, @[ "Retry the download", "Check if the file exists at the URL", "Try using 'nip fetch --retry=3'" ] ) proc repositoryUnavailableError*(repoUrl: string): NimPakError = newNimPakError( RepositoryUnavailable, fmt"Repository at '{repoUrl}' is unavailable", "Could not connect to the package repository", @[ "Check repository status at the provider's site", "Try an alternative mirror", "Verify DNS resolution works" ] ) proc timeoutError*(operation: string, timeoutSeconds: int): NimPakError = newNimPakError( TimeoutError, fmt"Operation '{operation}' timed out after {timeoutSeconds}s", "The operation took too long to complete", @[ "Retry with a longer timeout: --timeout=", "Check network latency to the server", "Try during off-peak hours" ] ) # --- Build Errors --- proc buildFailedError*(packageName: string, stage: string, output: string = ""): NimPakError = let context = if output.len > 0: output[0..min(200, output.len-1)] else: stage newNimPakError( BuildFailed, fmt"Build failed for package '{packageName}' at stage '{stage}'", context, @[ "Check the build log for details", "Verify build dependencies are installed", "Try 'nip build --verbose' for more information" ] ) proc missingDependencyError*(packageName: string, depName: string): NimPakError = newNimPakError( MissingDependency, fmt"Missing dependency '{depName}' for package '{packageName}'", "A required dependency could not be resolved", @[ fmt"Install the dependency first: nip install {depName}", "Check if the dependency exists in configured repositories", "Verify version constraints in the package manifest" ] ) # --- ACUL/Policy Errors --- proc signatureInvalidError*(packageName: string, keyId: string = ""): NimPakError = let context = if keyId.len > 0: fmt"Key ID: {keyId}" else: "No valid signature found" newNimPakError( SignatureInvalid, fmt"Signature verification failed for '{packageName}'", context, @[ "The package may have been tampered with", "Update trusted keys with 'nip trust update'", "Re-download from an official source" ] ) proc trustViolationError*(keyId: string, reason: string): NimPakError = newNimPakError( TrustViolation, fmt"Trust chain violation for key '{keyId}'", reason, @[ "Verify the key in your trust store", "Check for key revocation", "Contact the package maintainer" ] ) proc aculViolationError*(packageName: string, violation: string): NimPakError = newNimPakError( AculViolation, fmt"ACUL policy violation for package '{packageName}'", violation, @[ "Review the ACUL policy requirements", "Check if the package meets compliance standards", "Contact your administrator for policy exceptions" ] ) # --- Storage Errors --- proc objectNotFoundError*(hash: string): NimPakError = newNimPakError( ObjectNotFound, fmt"Object not found in CAS: {hash[0..min(20, hash.len-1)]}...", "The requested object does not exist in any CAS location", @[ "Run garbage collection verification: nip gc --verify", "Re-install the package that requires this object", "Check CAS integrity with 'nip cas verify'" ] ) proc storageFull*(path: string, required: int64, available: int64): NimPakError = newNimPakError( StorageFull, "Insufficient storage space", fmt"Required: {required div (1024*1024)}MB, Available: {available div (1024*1024)}MB at {path}", @[ "Free up disk space", "Run garbage collection: nip gc", "Move CAS to a larger partition" ] ) # --- GC Errors --- proc gcFailedError*(reason: string): NimPakError = newNimPakError( GarbageCollectionFailed, "Garbage collection failed", reason, @[ "Check for locked files or active operations", "Retry with 'nip gc --force'", "Verify CAS integrity first: nip cas verify" ] ) proc referenceIntegrityError*(hash: string, expectedRefs: int, actualRefs: int): NimPakError = newNimPakError( ReferenceIntegrityError, fmt"Reference count mismatch for object {hash[0..min(12, hash.len-1)]}...", fmt"Expected: {expectedRefs}, Actual: {actualRefs}", @[ "Run reference rebuild: nip gc --rebuild-refs", "This may indicate storage corruption" ] ) # --- Transaction Errors --- proc transactionFailedError*(txId: string, operation: string): NimPakError = newNimPakError( TransactionFailed, fmt"Transaction '{txId}' failed during '{operation}'", "The atomic operation could not complete", @[ "Automatic rollback was attempted", "Check system logs for details", "Retry the operation" ] ) proc rollbackFailedError*(txId: string, reason: string): NimPakError = newNimPakError( RollbackFailed, fmt"Rollback failed for transaction '{txId}'", reason, @[ "Manual intervention may be required", "Check partial state in /var/lib/nip/transactions/", "Contact support if data loss occurred" ] ) # ############################################################################ # Error Recovery Strategies # ############################################################################ type RecoveryStrategy* = enum Retry, Skip, Abort, Fallback, Manual RecoveryAction* = object strategy*: RecoveryStrategy message*: string action*: proc(): bool {.closure.} proc suggestRecovery*(err: NimPakError): RecoveryStrategy = ## Suggest a recovery strategy based on error type case err.code of NetworkError, DownloadFailed, TimeoutError: Retry of PackageNotFound, ObjectNotFound: Fallback of PermissionDenied, ElevationRequired: Manual of ChecksumMismatch, SignatureInvalid: Abort else: Abort proc isRecoverable*(err: NimPakError): bool = ## Check if error can potentially be recovered from err.code in {NetworkError, DownloadFailed, TimeoutError, RepositoryUnavailable, PackageNotFound} # ############################################################################ # Error Chaining # ############################################################################ proc wrapError*(cause: NimPakError, code: ErrorCode, message: string): NimPakError = ## Wrap an existing error with additional context result = newNimPakError( code, message, fmt"Caused by: {cause.msg}", cause.suggestions ) proc chain*(errors: varargs[NimPakError]): string = ## Format a chain of related errors result = "Error chain:\n" for i, err in errors: result.add fmt" {i+1}. [{err.code}] {err.msg}\n"