Skip to main content

virtuoso_cli/
error.rs

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>;