diff --git a/Sources/tart/Commands/IP.swift b/Sources/tart/Commands/IP.swift index beca08b..131ba19 100644 --- a/Sources/tart/Commands/IP.swift +++ b/Sources/tart/Commands/IP.swift @@ -19,7 +19,7 @@ struct IP: AsyncParsableCommand { let vmMACAddress = MACAddress(fromString: vmConfig.macAddress.string)! guard let ipViaDHCP = try await IP.resolveIP(vmMACAddress, secondsToWait: wait) else { - throw RuntimeError("no IP address found, is your VM running?") + throw RuntimeError.NoIPAddressFound("no IP address found, is your VM running?") } let arpCache = try ARPCache() diff --git a/Sources/tart/Commands/Login.swift b/Sources/tart/Commands/Login.swift index f68777b..8c98f43 100644 --- a/Sources/tart/Commands/Login.swift +++ b/Sources/tart/Commands/Login.swift @@ -47,7 +47,7 @@ struct Login: AsyncParsableCommand { credentialsProviders: [credentialsProvider]) try await registry.ping() } catch { - throw RuntimeError("invalid credentials: \(error)") + throw RuntimeError.InvalidCredentials("invalid credentials: \(error)") } try KeychainCredentialsProvider().store(host: host, user: user, password: password) diff --git a/Sources/tart/Commands/Run.swift b/Sources/tart/Commands/Run.swift index 6a80ca8..f7bbedb 100644 --- a/Sources/tart/Commands/Run.swift +++ b/Sources/tart/Commands/Run.swift @@ -117,7 +117,7 @@ struct Run: AsyncParsableCommand { } if try !FileLock(lockURL: additionalDiskAttachment.url).trylock() { - throw RuntimeError("disk \(additionalDiskAttachment.url.path) seems to be already in use, " + throw RuntimeError.DiskAlreadyInUse("disk \(additionalDiskAttachment.url.path) seems to be already in use, " + "unmount it first in Finder") } } @@ -154,7 +154,7 @@ struct Run: AsyncParsableCommand { // [1]: https://man.openbsd.org/fcntl let lock = try PIDLock(lockURL: vmDir.configURL) if try !lock.trylock() { - throw RuntimeError("Virtual machine \"\(name)\" is already running!", exitCode: 2) + throw RuntimeError.VMAlreadyRunning("VM \"\(name)\" is already running!") } let task = Task { diff --git a/Sources/tart/Commands/Stop.swift b/Sources/tart/Commands/Stop.swift index 7e7306f..1ecc0d0 100644 --- a/Sources/tart/Commands/Stop.swift +++ b/Sources/tart/Commands/Stop.swift @@ -19,7 +19,7 @@ struct Stop: AsyncParsableCommand { // Find the VM's PID var pid = try lock.pid() if pid == 0 { - throw RuntimeError("VM \(name) is not running", exitCode: 2) + throw RuntimeError.VMNotRunning("VM \"\(name)\" is not running") } // Try to gracefully terminate the VM @@ -54,7 +54,7 @@ struct Stop: AsyncParsableCommand { if ret != 0 { let details = Errno(rawValue: CInt(errno)) - throw RuntimeError("failed to forcefully terminate the VM \(name): \(details)") + throw RuntimeError.VMTerminationFailed("failed to forcefully terminate the VM \"\(name)\": \(details)") } } } diff --git a/Sources/tart/OCI/RemoteName.swift b/Sources/tart/OCI/RemoteName.swift index 2ddb35e..3fbf2ea 100644 --- a/Sources/tart/OCI/RemoteName.swift +++ b/Sources/tart/OCI/RemoteName.swift @@ -106,7 +106,7 @@ struct RemoteName: Comparable, Hashable, CustomStringConvertible { try ParseTreeWalker().walk(referenceCollector, try parser.root()) if let error = errorCollector.error { - throw RuntimeError("failed to parse remote name: \(error)") + throw RuntimeError.FailedToParseRemoteName("\(error)") } host = referenceCollector.host! @@ -120,7 +120,7 @@ struct RemoteName: Comparable, Hashable, CustomStringConvertible { } else if reference.starts(with: ":") { self.reference = Reference(tag: String(reference.dropFirst(1))) } else { - throw RuntimeError("failed to parse remote name: unknown reference format") + throw RuntimeError.FailedToParseRemoteName("unknown reference format") } } else { self.reference = Reference(tag: "latest") diff --git a/Sources/tart/PIDLock.swift b/Sources/tart/PIDLock.swift index 7800059..f65c417 100644 --- a/Sources/tart/PIDLock.swift +++ b/Sources/tart/PIDLock.swift @@ -44,7 +44,7 @@ class PIDLock { let details = Errno(rawValue: CInt(errno)) - throw RuntimeError("\(message): \(details)") + throw RuntimeError.PIDLockFailed("\(message): \(details)") } return (true, result) diff --git a/Sources/tart/Root.swift b/Sources/tart/Root.swift index c7ee3e9..5becce5 100644 --- a/Sources/tart/Root.swift +++ b/Sources/tart/Root.swift @@ -106,8 +106,8 @@ struct Root: AsyncParsableCommand { print(error) - if let runtimeError = error as? RuntimeError { - Foundation.exit(runtimeError.exitCode) + if let errorWithExitCode = error as? HasExitCode { + Foundation.exit(errorWithExitCode.exitCode) } Foundation.exit(1) diff --git a/Sources/tart/URL+AccessDate.swift b/Sources/tart/URL+AccessDate.swift index 0e62398..3d43d20 100644 --- a/Sources/tart/URL+AccessDate.swift +++ b/Sources/tart/URL+AccessDate.swift @@ -13,7 +13,7 @@ extension URL { let times = [accessDate.asTimeval(), modificationDate.asTimeval()] let ret = utimes(path, times) if ret != 0 { - throw RuntimeError("utimes(2) failed: \(ret.explanation())") + throw RuntimeError.FailedToUpdateAccessDate("utimes(2) failed: \(ret.explanation())") } } } diff --git a/Sources/tart/VMDirectory.swift b/Sources/tart/VMDirectory.swift index 8ce8bce..617f72d 100644 --- a/Sources/tart/VMDirectory.swift +++ b/Sources/tart/VMDirectory.swift @@ -41,7 +41,7 @@ struct VMDirectory: Prunable { func initialize(overwrite: Bool = false) throws { if !overwrite && initialized { - throw RuntimeError("VM directory is already initialized, preventing overwrite") + throw RuntimeError.VMDirectoryAlreadyInitialized("VM directory is already initialized, preventing overwrite") } try FileManager.default.createDirectory(at: baseURL, withIntermediateDirectories: true, attributes: nil) @@ -53,11 +53,11 @@ struct VMDirectory: Prunable { func validate() throws { if !FileManager.default.fileExists(atPath: baseURL.path) { - throw RuntimeError("the specified VM does not exist") + throw RuntimeError.VMDoesNotExist(name: baseURL.lastPathComponent) } if !initialized { - throw RuntimeError("VM is missing some of its files (\(configURL.lastPathComponent)," + throw RuntimeError.VMMissingFiles("VM is missing some of its files (\(configURL.lastPathComponent)," + " \(diskURL.lastPathComponent) or \(nvramURL.lastPathComponent))") } } diff --git a/Sources/tart/VMStorageHelper.swift b/Sources/tart/VMStorageHelper.swift index a214c69..2dde162 100644 --- a/Sources/tart/VMStorageHelper.swift +++ b/Sources/tart/VMStorageHelper.swift @@ -26,7 +26,7 @@ class VMStorageHelper { return try closure() } catch { if error.isFileNotFound() { - throw RuntimeError("source VM \"\(name)\" not found, is it listed in \"tart list\"?") + throw RuntimeError.VMDoesNotExist(name: name) } throw error @@ -40,17 +40,66 @@ extension Error { } } -class RuntimeError: Error, CustomStringConvertible { - let message: String - let exitCode: Int32 +enum RuntimeError : Error { + case VMDoesNotExist(name: String) + case VMMissingFiles(_ message: String) + case VMNotRunning(_ message: String) + case VMAlreadyRunning(_ message: String) + case NoIPAddressFound(_ message: String) + case DiskAlreadyInUse(_ message: String) + case FailedToUpdateAccessDate(_ message: String) + case PIDLockFailed(_ message: String) + case FailedToParseRemoteName(_ message: String) + case VMTerminationFailed(_ message: String) + case InvalidCredentials(_ message: String) + case VMDirectoryAlreadyInitialized(_ message: String) +} - init(_ message: String, exitCode: Int32 = 1) { - self.message = message - self.exitCode = exitCode +protocol HasExitCode { + var exitCode: Int32 { get } +} + +extension RuntimeError : CustomStringConvertible { + public var description: String { + switch self { + case .VMDoesNotExist(let name): + return "the specified VM \"\(name)\" does not exist" + case .VMMissingFiles(let message): + return message + case .VMNotRunning(let message): + return message + case .VMAlreadyRunning(let message): + return message + case .NoIPAddressFound(let message): + return message + case .DiskAlreadyInUse(let message): + return message + case .FailedToUpdateAccessDate(let message): + return message + case .PIDLockFailed(let message): + return message + case .FailedToParseRemoteName(let cause): + return "failed to parse remote name: \(cause)" + case .VMTerminationFailed(let message): + return message + case .InvalidCredentials(let message): + return message + case .VMDirectoryAlreadyInitialized(let message): + return message + } } +} - var description: String { - message +extension RuntimeError : HasExitCode { + var exitCode: Int32 { + switch self { + case .VMNotRunning: + return 2 + case .VMAlreadyRunning: + return 2 + default: + return 1 + } } } @@ -60,7 +109,7 @@ class RuntimeError: Error, CustomStringConvertible { extension RuntimeError : CustomNSError { var errorUserInfo: [String : Any] { [ - NSDebugDescriptionErrorKey: message, + NSDebugDescriptionErrorKey: description, ] } }