Skip to main content

alpine_protocol_sdk/
error.rs

1use alpine::handshake::HandshakeError;
2use alpine::stream::StreamError;
3use thiserror::Error;
4
5use crate::transport::TransportError;
6
7/// Errors emitted by the SDK runtime.
8#[derive(Error, Debug)]
9pub enum AlpineSdkError {
10    #[error("transport error: {0}")]
11    Transport(#[from] TransportError),
12
13    #[error("connection timed out")]
14    Timeout,
15
16    #[error("discovery failed: {0}")]
17    DiscoveryFailed(String),
18
19    #[error("invalid discovery reply")]
20    InvalidDiscoveryReply,
21
22    #[error("handshake already in progress")]
23    HandshakeAlreadyInProgress,
24
25    #[error("discovery not allowed once handshake has begun")]
26    DiscoveryAfterHandshake,
27
28    #[error("invalid phase transition: {0}")]
29    InvalidPhaseTransition(String),
30
31    #[error("missing client_nonce")]
32    MissingClientNonce,
33
34    #[error("handshake failed: {0}")]
35    HandshakeFailed(String),
36
37    #[error("device identity verification failed")]
38    IdentityVerificationFailed,
39
40    #[error("device identity not trusted: {0}")]
41    UntrustedDevice(String),
42
43    #[error("io error: {0}")]
44    Io(String),
45
46    #[error("no active session")]
47    NoActiveSession,
48
49    #[error("session expired")]
50    SessionExpired,
51
52    #[error("streaming not supported by device")]
53    StreamingNotSupported,
54
55    #[error("invalid channel value")]
56    InvalidChannelValue,
57
58    #[error("probe failed: {0}")]
59    ProbeFailed(String),
60
61    #[error("invalid capabilities: {0}")]
62    InvalidCapabilities(String),
63
64    #[error("dangerous control command blocked (enable allow_dangerous)")]
65    DangerousControlDisallowed,
66
67    #[error("sensitive operation requires trusted identity")]
68    SensitiveOperationRequiresTrust,
69
70    #[error("vendor extension not registered: {0}")]
71    VendorExtensionNotRegistered(String),
72
73    #[error("unsupported environment: {0}")]
74    UnsupportedEnvironment(String),
75
76    #[error("incompatible protocol: {0}")]
77    IncompatibleProtocol(String),
78
79    #[error("device quarantined: {0}")]
80    Quarantined(String),
81
82    #[error("status mismatch: {0}")]
83    StatusMismatch(String),
84
85    #[error("device selection denied: {0}")]
86    SelectionDenied(String),
87
88    #[error("internal error: {0}")]
89    Internal(String),
90}
91
92impl AlpineSdkError {
93    pub fn user_hint(&self) -> Option<&'static str> {
94        match self {
95            AlpineSdkError::Timeout => Some("request timed out; check network reachability"),
96            AlpineSdkError::DiscoveryFailed(_) => {
97                Some("discovery failed; confirm the device is on the same network")
98            }
99            AlpineSdkError::HandshakeFailed(_) => {
100                Some("handshake failed; verify credentials and device time")
101            }
102            AlpineSdkError::UntrustedDevice(_) => {
103                Some("device identity not trusted; verify trust bundle")
104            }
105            AlpineSdkError::ProbeFailed(_) => Some("device probe failed; verify device health"),
106            AlpineSdkError::DangerousControlDisallowed => {
107                Some("dangerous control blocked; enable allow_dangerous to proceed")
108            }
109            AlpineSdkError::SensitiveOperationRequiresTrust => {
110                Some("operation requires a trusted device identity")
111            }
112            AlpineSdkError::VendorExtensionNotRegistered(_) => {
113                Some("vendor extension not registered; register before use")
114            }
115            AlpineSdkError::UnsupportedEnvironment(_) => {
116                Some("environment not supported for UDP discovery/control")
117            }
118            AlpineSdkError::IncompatibleProtocol(_) => {
119                Some("device protocol version is incompatible with this SDK")
120            }
121            AlpineSdkError::Quarantined(_) => {
122                Some("device is quarantined; observe-only mode enforced")
123            }
124            AlpineSdkError::InvalidCapabilities(_) => {
125                Some("requested capabilities are not supported by the device")
126            }
127            AlpineSdkError::StatusMismatch(_) => {
128                Some("device does not support standard status; use vendor status helper")
129            }
130            AlpineSdkError::SelectionDenied(_) => Some("device rejected by selection policy"),
131            _ => None,
132        }
133    }
134
135    pub fn internal_cause(&self) -> Option<&str> {
136        match self {
137            AlpineSdkError::DiscoveryFailed(detail) => Some(detail.as_str()),
138            AlpineSdkError::HandshakeFailed(detail) => Some(detail.as_str()),
139            AlpineSdkError::UntrustedDevice(detail) => Some(detail.as_str()),
140            AlpineSdkError::Io(detail) => Some(detail.as_str()),
141            AlpineSdkError::ProbeFailed(detail) => Some(detail.as_str()),
142            AlpineSdkError::InvalidCapabilities(detail) => Some(detail.as_str()),
143            AlpineSdkError::VendorExtensionNotRegistered(detail) => Some(detail.as_str()),
144            AlpineSdkError::UnsupportedEnvironment(detail) => Some(detail.as_str()),
145            AlpineSdkError::IncompatibleProtocol(detail) => Some(detail.as_str()),
146            AlpineSdkError::Quarantined(detail) => Some(detail.as_str()),
147            AlpineSdkError::StatusMismatch(detail) => Some(detail.as_str()),
148            AlpineSdkError::SelectionDenied(detail) => Some(detail.as_str()),
149            AlpineSdkError::Internal(detail) => Some(detail.as_str()),
150            _ => None,
151        }
152    }
153
154    pub fn with_context(
155        self,
156        device_id: Option<String>,
157        ip: Option<String>,
158        port: Option<u16>,
159        operation: Option<String>,
160    ) -> SdkErrorContext {
161        SdkErrorContext::with_context(self, device_id, ip, port, operation)
162    }
163}
164
165#[derive(Debug)]
166pub struct SdkErrorContext {
167    pub error: AlpineSdkError,
168    pub device_id: Option<String>,
169    pub ip: Option<String>,
170    pub port: Option<u16>,
171    pub operation: Option<String>,
172}
173
174impl SdkErrorContext {
175    pub fn new(error: AlpineSdkError) -> Self {
176        Self {
177            error,
178            device_id: None,
179            ip: None,
180            port: None,
181            operation: None,
182        }
183    }
184
185    pub fn with_context(
186        error: AlpineSdkError,
187        device_id: Option<String>,
188        ip: Option<String>,
189        port: Option<u16>,
190        operation: Option<String>,
191    ) -> Self {
192        Self {
193            error,
194            device_id,
195            ip,
196            port,
197            operation,
198        }
199    }
200}
201
202impl std::fmt::Display for SdkErrorContext {
203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204        write!(f, "{}", self.error)?;
205        if self.device_id.is_none()
206            && self.ip.is_none()
207            && self.port.is_none()
208            && self.operation.is_none()
209        {
210            return Ok(());
211        }
212        write!(f, " (")?;
213        let mut first = true;
214        if let Some(device_id) = &self.device_id {
215            write!(f, "device_id={}", device_id)?;
216            first = false;
217        }
218        if let Some(ip) = &self.ip {
219            if !first {
220                write!(f, ", ")?;
221            }
222            write!(f, "ip={}", ip)?;
223            first = false;
224        }
225        if let Some(port) = self.port {
226            if !first {
227                write!(f, ", ")?;
228            }
229            write!(f, "port={}", port)?;
230            first = false;
231        }
232        if let Some(operation) = &self.operation {
233            if !first {
234                write!(f, ", ")?;
235            }
236            write!(f, "op={}", operation)?;
237        }
238        write!(f, ")")
239    }
240}
241
242impl std::error::Error for SdkErrorContext {}
243
244impl From<HandshakeError> for AlpineSdkError {
245    fn from(err: HandshakeError) -> Self {
246        match err {
247            HandshakeError::Transport(_) => AlpineSdkError::HandshakeFailed(err.to_string()),
248            other => AlpineSdkError::HandshakeFailed(other.to_string()),
249        }
250    }
251}
252
253impl From<StreamError> for AlpineSdkError {
254    fn from(err: StreamError) -> Self {
255        AlpineSdkError::Internal(err.to_string())
256    }
257}