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    #[error("daemon not ready: {0}")]
26    DaemonNotReady(String),
27
28    #[error("config error: {0}")]
29    Config(String),
30
31    #[error("not found: {0}")]
32    NotFound(String),
33
34    #[error("conflict: {0}")]
35    Conflict(String),
36}
37
38impl VirtuosoError {
39    pub fn exit_code(&self) -> i32 {
40        match self {
41            Self::Config(_) => exit_codes::USAGE_ERROR,
42            Self::NotFound(_) => exit_codes::NOT_FOUND,
43            Self::Conflict(_) => exit_codes::CONFLICT,
44            Self::Connection(_) | Self::Ssh(_) | Self::Timeout(_) | Self::DaemonNotReady(_) => {
45                exit_codes::GENERAL_ERROR
46            }
47            Self::Execution(_) | Self::Io(_) | Self::Json(_) => exit_codes::GENERAL_ERROR,
48        }
49    }
50
51    pub fn error_type(&self) -> &'static str {
52        match self {
53            Self::Connection(_) => "connection_failed",
54            Self::Execution(_) => "execution_failed",
55            Self::Ssh(_) => "ssh_error",
56            Self::Io(_) => "io_error",
57            Self::Json(_) => "json_error",
58            Self::Timeout(_) => "timeout",
59            Self::DaemonNotReady(_) => "daemon_not_ready",
60            Self::Config(_) => "config_error",
61            Self::NotFound(_) => "not_found",
62            Self::Conflict(_) => "conflict",
63        }
64    }
65
66    pub fn retryable(&self) -> bool {
67        matches!(
68            self,
69            Self::Connection(_) | Self::Timeout(_) | Self::DaemonNotReady(_)
70        )
71    }
72
73    pub fn suggestion(&self) -> Option<String> {
74        match self {
75            Self::Config(msg) if msg.contains("VB_REMOTE_HOST") => {
76                Some("Run: virtuoso init".into())
77            }
78            Self::DaemonNotReady(_) | Self::Connection(_) => {
79                Some("Run: virtuoso tunnel start".into())
80            }
81            Self::Timeout(secs) => Some(format!("Retry with --timeout {}", secs * 2)),
82            Self::Ssh(msg) if msg.contains("authentication") => {
83                Some("Check SSH keys: ssh-add -l".into())
84            }
85            _ => None,
86        }
87    }
88
89    pub fn to_cli_error(&self) -> CliError {
90        CliError {
91            error: self.error_type().to_string(),
92            message: self.to_string(),
93            suggestion: self.suggestion(),
94            retryable: self.retryable(),
95        }
96    }
97}
98
99pub type Result<T> = std::result::Result<T, VirtuosoError>;