1use crate::exit_codes;
2use crate::output::CliError;
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum VirtuosoError {
7 #[error("connection failed: {0}")]
8 Connection(String),
9
10 #[error("execution failed: {0}")]
11 Execution(String),
12
13 #[error("ssh error: {0}")]
14 Ssh(String),
15
16 #[error("io error: {0}")]
17 Io(#[from] std::io::Error),
18
19 #[error("json error: {0}")]
20 Json(#[from] serde_json::Error),
21
22 #[error("timeout after {0}s")]
23 Timeout(u64),
24
25 #[allow(dead_code)]
26 #[error("daemon not ready: {0}")]
27 DaemonNotReady(String),
28
29 #[error("config error: {0}")]
30 Config(String),
31
32 #[error("not found: {0}")]
33 NotFound(String),
34
35 #[error("conflict: {0}")]
36 Conflict(String),
37}
38
39impl VirtuosoError {
40 pub fn exit_code(&self) -> i32 {
41 match self {
42 Self::Config(_) => exit_codes::USAGE_ERROR,
43 Self::NotFound(_) => exit_codes::NOT_FOUND,
44 Self::Conflict(_) => exit_codes::CONFLICT,
45 Self::Connection(_) | Self::Ssh(_) | Self::Timeout(_) | Self::DaemonNotReady(_) => {
46 exit_codes::GENERAL_ERROR
47 }
48 Self::Execution(_) | Self::Io(_) | Self::Json(_) => exit_codes::GENERAL_ERROR,
49 }
50 }
51
52 pub fn error_type(&self) -> &'static str {
53 match self {
54 Self::Connection(_) => "connection_failed",
55 Self::Execution(_) => "execution_failed",
56 Self::Ssh(_) => "ssh_error",
57 Self::Io(_) => "io_error",
58 Self::Json(_) => "json_error",
59 Self::Timeout(_) => "timeout",
60 Self::DaemonNotReady(_) => "daemon_not_ready",
61 Self::Config(_) => "config_error",
62 Self::NotFound(_) => "not_found",
63 Self::Conflict(_) => "conflict",
64 }
65 }
66
67 pub fn retryable(&self) -> bool {
68 matches!(
69 self,
70 Self::Connection(_) | Self::Timeout(_) | Self::DaemonNotReady(_)
71 )
72 }
73
74 pub fn suggestion(&self) -> Option<String> {
75 match self {
76 Self::Config(msg) if msg.contains("VB_REMOTE_HOST") => {
77 Some("Run: virtuoso init".into())
78 }
79 Self::DaemonNotReady(_) | Self::Connection(_) => {
80 Some("Run: virtuoso tunnel start".into())
81 }
82 Self::Timeout(secs) => Some(format!("Retry with --timeout {}", secs * 2)),
83 Self::Ssh(msg) if msg.contains("authentication") => {
84 Some("Check SSH keys: ssh-add -l".into())
85 }
86 _ => None,
87 }
88 }
89
90 pub fn to_cli_error(&self) -> CliError {
91 CliError {
92 error: self.error_type().to_string(),
93 message: self.to_string(),
94 suggestion: self.suggestion(),
95 retryable: self.retryable(),
96 }
97 }
98}
99
100pub type Result<T> = std::result::Result<T, VirtuosoError>;