fetch_ml/cli/src/errors.zig
Jeremie Fraeys d225ea1f00 feat: implement Zig CLI with comprehensive ML experiment management
- Add modern CLI interface built with Zig for performance
- Include TUI (Terminal User Interface) with bubbletea-like features
- Implement ML experiment commands (run, status, manage)
- Add configuration management and validation
- Include shell completion scripts for bash and zsh
- Add comprehensive CLI testing framework
- Support for multiple ML frameworks and project types

CLI provides fast, efficient interface for ML experiment management
with modern terminal UI and comprehensive feature set.
2025-12-04 16:53:58 -05:00

206 lines
8 KiB
Zig

const std = @import("std");
const colors = @import("utils/colors.zig");
const ws = @import("net/ws.zig");
const crypto = @import("utils/crypto.zig");
/// User-friendly error types for CLI
pub const CLIError = error{
// Configuration errors
ConfigNotFound,
ConfigInvalid,
APIKeyMissing,
APIKeyInvalid,
// Network errors
ConnectionFailed,
ServerUnreachable,
AuthenticationFailed,
RequestTimeout,
// Command errors
InvalidArguments,
MissingCommit,
CommandFailed,
JobNotFound,
PermissionDenied,
ResourceExists,
JobAlreadyRunning,
JobCancelled,
ServerError,
SyncFailed,
// System errors
OutOfMemory,
FileSystemError,
ProcessError,
};
/// Error message mapping
pub const ErrorMessages = struct {
pub fn getMessage(err: anyerror) []const u8 {
return switch (err) {
// Configuration errors
error.ConfigNotFound => "Configuration file not found. Run 'ml init' to create one.",
error.ConfigInvalid => "Configuration file is invalid. Please check your settings.",
error.APIKeyMissing => "API key not configured. Set it in your config file.",
error.APIKeyInvalid => "API key is invalid. Please check your credentials.",
error.InvalidAPIKeyFormat => "API key format is invalid. Expected 64-character hash.",
// Network errors
error.ConnectionFailed => "Failed to connect to server. Check if the server is running.",
error.ServerUnreachable => "Server is unreachable. Verify network connectivity.",
error.AuthenticationFailed => "Authentication failed. Check your API key.",
error.RequestTimeout => "Request timed out. Server may be busy.",
// Command errors
error.InvalidArguments => "Invalid command arguments. Use --help for usage.",
error.MissingCommit => "Missing commit ID. Use --commit <id> to specify the commit.",
error.CommandFailed => "Command failed. Check server logs for details.",
error.JobNotFound => "Job not found. Verify the job name.",
error.PermissionDenied => "Permission denied. Check your user permissions.",
error.ResourceExists => "Resource already exists.",
error.JobAlreadyRunning => "Job is already running.",
error.JobCancelled => "Job was cancelled.",
error.PruneFailed => "Prune operation failed. Check server logs for details.",
error.ServerError => "Server error occurred. Check server logs for details.",
error.SyncFailed => "Sync operation failed.",
// System errors
error.OutOfMemory => "Out of memory. Close other applications and try again.",
error.FileSystemError => "File system error. Check disk space and permissions.",
error.ProcessError => "Process error. Try again or contact support.",
// WebSocket specific errors
error.InvalidURL => "Invalid server URL. Check your configuration.",
error.TLSNotSupported => "TLS (HTTPS) not supported in this build.",
error.ConnectionRefused => "Connection refused. Server may not be running.",
error.NetworkUnreachable => "Network unreachable. Check your internet connection.",
error.InvalidFrame => "Invalid WebSocket frame. Protocol error.",
error.EndpointNotFound => "WebSocket endpoint not found. Server may not be running or is misconfigured.",
error.ServerUnavailable => "Server is temporarily unavailable.",
error.HandshakeFailed => "WebSocket handshake failed.",
// Default fallback
else => "An unexpected error occurred. Please try again or contact support.",
};
}
/// Check if error is user-fixable
pub fn isUserFixable(err: anyerror) bool {
return switch (err) {
error.ConfigNotFound,
error.ConfigInvalid,
error.APIKeyMissing,
error.APIKeyInvalid,
error.InvalidArguments,
error.MissingCommit,
error.JobNotFound,
error.ResourceExists,
error.JobAlreadyRunning,
error.JobCancelled,
error.SyncFailed,
=> true,
error.ConnectionFailed,
error.ServerUnreachable,
error.AuthenticationFailed,
error.RequestTimeout,
error.ConnectionRefused,
error.NetworkUnreachable,
=> true,
else => false,
};
}
/// Get suggestion for fixing the error
pub fn getSuggestion(err: anyerror) ?[]const u8 {
return switch (err) {
error.ConfigNotFound => "Run 'ml init' to create a configuration file.",
error.APIKeyMissing => "Add your API key to the configuration file.",
error.ConnectionFailed => "Start the API server with 'api-server' or check if it's running.",
error.AuthenticationFailed => "Verify your API key in the configuration.",
error.InvalidArguments => "Use 'ml <command> --help' for correct usage.",
error.MissingCommit => "Use --commit <id> to specify the commit ID for your job.",
error.JobNotFound => "List available jobs with 'ml status'.",
error.ResourceExists => "Use a different name or remove the existing resource.",
error.JobAlreadyRunning => "Wait for the current job to finish or cancel it first.",
error.JobCancelled => "The job was cancelled. You can restart it if needed.",
error.SyncFailed => "Check network connectivity and server status.",
else => null,
};
}
};
/// Error handler for CLI commands
pub const ErrorHandler = struct {
/// Send crash report to server for non-user-fixable errors
fn sendCrashReport() void {
return;
}
pub fn display(self: ErrorHandler, err: anyerror, context: ?[]const u8) void {
_ = self; // Self not used in current implementation
const message = ErrorMessages.getMessage(err);
const suggestion = ErrorMessages.getSuggestion(err);
colors.printError("Error: {s}\n", .{message});
if (context) |ctx| {
colors.printWarning("Context: {s}\n", .{ctx});
}
if (suggestion) |sug| {
colors.printInfo("Suggestion: {s}\n", .{sug});
}
if (ErrorMessages.isUserFixable(err)) {
colors.printInfo("This error can be fixed by updating your configuration.\n", .{});
}
}
pub fn handleCommandError(err: anyerror, command: []const u8) void {
_ = command; // TODO: Use command in crash report
// Send crash report for non-user-fixable errors
sendCrashReport();
const message = ErrorMessages.getMessage(err);
const suggestion = ErrorMessages.getSuggestion(err);
const is_fixable = ErrorMessages.isUserFixable(err);
colors.printError("Error: {s}\n", .{message});
if (suggestion) |sug| {
colors.printInfo("Suggestion: {s}\n", .{sug});
}
if (is_fixable) {
colors.printInfo("This is a user-fixable issue.\n", .{});
} else {
colors.printWarning("If this persists, check server logs or contact support.\n", .{});
}
// Exit with appropriate code
std.process.exit(if (is_fixable) 2 else 1);
}
pub fn handleNetworkError(err: anyerror, operation: []const u8) void {
std.debug.print("Network error during {s}: {s}\n", .{ operation, ErrorMessages.getMessage(err) });
if (ErrorMessages.getSuggestion(err)) |sug| {
std.debug.print("Try: {s}\n", .{sug});
}
std.process.exit(3);
}
pub fn handleConfigError(err: anyerror) void {
std.debug.print("Configuration error: {s}\n", .{ErrorMessages.getMessage(err)});
if (ErrorMessages.getSuggestion(err)) |sug| {
std.debug.print("Fix: {s}\n", .{sug});
}
std.process.exit(4);
}
};