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