Skip to main content

detrix_rs/
error.rs

1//! Error types for the Detrix client.
2
3use thiserror::Error;
4
5/// Error type for Detrix client operations.
6#[derive(Debug, Error)]
7#[allow(clippy::enum_variant_names)]
8pub enum Error {
9    /// Client has already been initialized.
10    #[error("detrix client already initialized")]
11    AlreadyInitialized,
12
13    /// Client has not been initialized.
14    #[error("detrix client not initialized")]
15    NotInitialized,
16
17    /// Wake operation is already in progress.
18    #[error("wake operation already in progress")]
19    WakeInProgress,
20
21    /// lldb-dap binary not found.
22    #[error("lldb-dap not found: {0}")]
23    LldbNotFound(String),
24
25    /// Failed to start lldb-dap process.
26    #[error("failed to start lldb-dap: {0}")]
27    LldbStartFailed(String),
28
29    /// lldb-dap process exited unexpectedly.
30    #[error("lldb-dap process exited unexpectedly")]
31    LldbProcessDied,
32
33    /// Daemon is not reachable.
34    #[error("daemon not reachable at {url}: {message}")]
35    DaemonUnreachable { url: String, message: String },
36
37    /// Failed to register with daemon.
38    #[error("failed to register with daemon: {0}")]
39    RegistrationFailed(String),
40
41    /// Failed to start control plane server.
42    #[error("failed to start control plane: {0}")]
43    ControlPlaneError(String),
44
45    /// HTTP request failed.
46    #[error("HTTP request failed: {0}")]
47    HttpError(#[from] reqwest::Error),
48
49    /// IO error.
50    #[error("IO error: {0}")]
51    IoError(#[from] std::io::Error),
52
53    /// JSON serialization/deserialization error.
54    #[error("JSON error: {0}")]
55    JsonError(#[from] serde_json::Error),
56
57    /// Timeout waiting for operation.
58    #[error("timeout: {0}")]
59    Timeout(String),
60
61    /// Port bind error (address already in use).
62    #[error("port bind failed: {0}")]
63    PortBindError(String),
64
65    /// Configuration error (invalid paths, missing files, etc.).
66    #[error("configuration error: {0}")]
67    ConfigError(String),
68}
69
70/// Result type alias for Detrix client operations.
71pub type Result<T> = std::result::Result<T, Error>;
72
73impl<T> From<std::sync::PoisonError<T>> for Error {
74    fn from(_: std::sync::PoisonError<T>) -> Self {
75        Error::ControlPlaneError("lock poisoned".to_string())
76    }
77}
78
79/// Extension trait for adding context to any `Result<T, E: Display>`.
80pub(crate) trait ResultExt<T> {
81    fn control_plane(self, msg: &str) -> Result<T>;
82    fn lldb(self, msg: &str) -> Result<T>;
83    fn port_bind(self, msg: &str) -> Result<T>;
84    fn config(self, msg: &str) -> Result<T>;
85    fn registration(self, msg: &str) -> Result<T>;
86}
87
88impl<T, E: std::fmt::Display> ResultExt<T> for std::result::Result<T, E> {
89    fn control_plane(self, msg: &str) -> Result<T> {
90        self.map_err(|e| Error::ControlPlaneError(format!("{}: {}", msg, e)))
91    }
92
93    fn lldb(self, msg: &str) -> Result<T> {
94        self.map_err(|e| Error::LldbStartFailed(format!("{}: {}", msg, e)))
95    }
96
97    fn port_bind(self, msg: &str) -> Result<T> {
98        self.map_err(|e| Error::PortBindError(format!("{}: {}", msg, e)))
99    }
100
101    fn config(self, msg: &str) -> Result<T> {
102        self.map_err(|e| Error::ConfigError(format!("{}: {}", msg, e)))
103    }
104
105    fn registration(self, msg: &str) -> Result<T> {
106        self.map_err(|e| Error::RegistrationFailed(format!("{}: {}", msg, e)))
107    }
108}
109
110/// Extension trait for reqwest errors needing structured error construction.
111pub(crate) trait ReqwestResultExt<T> {
112    fn daemon_unreachable(self, url: &str) -> Result<T>;
113    fn registration_context(self) -> Result<T>;
114}
115
116impl<T> ReqwestResultExt<T> for std::result::Result<T, reqwest::Error> {
117    fn daemon_unreachable(self, url: &str) -> Result<T> {
118        self.map_err(|e| Error::DaemonUnreachable {
119            url: url.to_string(),
120            message: e.to_string(),
121        })
122    }
123
124    fn registration_context(self) -> Result<T> {
125        self.map_err(|e| {
126            use std::error::Error as StdError;
127            let mut details = format!("{}", e);
128            let err: &dyn StdError = &e;
129            if let Some(source) = err.source() {
130                details.push_str(&format!(" (caused by: {})", source));
131                if let Some(inner) = source.source() {
132                    details.push_str(&format!(" (inner: {})", inner));
133                }
134            }
135            Error::RegistrationFailed(details)
136        })
137    }
138}