const std = @import("std"); pub const NinaError = error{ PackageNotFound, MachineNotFound, GenerationNotFound, ConfigNotFound, ConfigParseError, BuildFailed, SshFailed, SearchFailed, NetworkError, OutOfMemory, ProcessFailed, Internal, // common IO errors that bubble up FileNotFound, AccessDenied, SystemResources, Unexpected, }; pub var error_info: ErrorInfo = .{}; pub const ErrorInfo = struct { message: [512]u8 = undefined, message_len: usize = 0, detail: [512]u8 = undefined, detail_len: usize = 0, suggestion: [256]u8 = undefined, suggestion_len: usize = 0, pub fn setMessage(self: *ErrorInfo, comptime fmt: []const u8, args: anytype) void { const r = std.fmt.bufPrint(&self.message, fmt, args) catch return; self.message_len = r.len; } pub fn setDetail(self: *ErrorInfo, comptime fmt: []const u8, args: anytype) void { const r = std.fmt.bufPrint(&self.detail, fmt, args) catch return; self.detail_len = r.len; } pub fn setSuggestion(self: *ErrorInfo, comptime fmt: []const u8, args: anytype) void { const r = std.fmt.bufPrint(&self.suggestion, fmt, args) catch return; self.suggestion_len = r.len; } pub fn getMessage(self: *const ErrorInfo) ?[]const u8 { if (self.message_len == 0) return null; return self.message[0..self.message_len]; } pub fn getDetail(self: *const ErrorInfo) ?[]const u8 { if (self.detail_len == 0) return null; return self.detail[0..self.detail_len]; } pub fn getSuggestion(self: *const ErrorInfo) ?[]const u8 { if (self.suggestion_len == 0) return null; return self.suggestion[0..self.suggestion_len]; } pub fn reset(self: *ErrorInfo) void { self.message_len = 0; self.detail_len = 0; self.suggestion_len = 0; } }; const BuildPattern = struct { pattern: []const u8, message: []const u8, suggestion: ?[]const u8 = null }; const build_patterns = [_]BuildPattern{ .{ .pattern = "undefined variable", .message = "package not found in nixpkgs", .suggestion = "nina search " }, .{ .pattern = "has been renamed to", .message = "package was renamed — update your config" }, .{ .pattern = "attribute '", .message = "package doesn't exist in this nixpkgs version" }, .{ .pattern = "collision between", .message = "two packages are claiming the same file — remove one" }, .{ .pattern = "infinite recursion", .message = "circular reference in configuration.nix — check recent edits" }, .{ .pattern = "No space left on device", .message = "disk is full", .suggestion = "nina clean" }, .{ .pattern = "hash mismatch", .message = "download checksum mismatch — try again" }, .{ .pattern = "cannot write lock file", .message = "can't write flake.lock", .suggestion = "use --no-write-lock-file" }, .{ .pattern = "SSL certificate", .message = "ssl error — check your network" }, .{ .pattern = "permission denied", .message = "permission denied — try with sudo" }, }; pub fn translateBuildError(stderr: []const u8) struct { message: []const u8, suggestion: ?[]const u8 } { for (build_patterns) |p| { if (std.mem.indexOf(u8, stderr, p.pattern) != null) { return .{ .message = p.message, .suggestion = p.suggestion }; } } return .{ .message = stderr, .suggestion = null }; } pub fn staticMessage(err: anyerror) []const u8 { return switch (err) { error.ConfigNotFound => "~/.nina.conf not found", error.ConfigParseError => "could not parse ~/.nina.conf", error.SearchFailed => "nix search failed", error.NetworkError => "network unavailable", error.OutOfMemory => "out of memory", error.ProcessFailed => "process failed", error.SshFailed => "ssh connection failed", error.MachineNotFound => "machine not found in config", error.PackageNotFound => "package not found", error.GenerationNotFound => "generation not found", error.BuildFailed => "build failed", else => "internal error", }; } test "build error translation" { const r = translateBuildError("error: undefined variable 'pkgs.notexist'"); try std.testing.expectEqualStrings("package not found in nixpkgs", r.message); } test "static message" { try std.testing.expectEqualStrings("~/.nina.conf not found", staticMessage(error.ConfigNotFound)); }