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