412 lines
13 KiB
Nim
412 lines
13 KiB
Nim
# 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=<seconds>",
|
|
"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" |