1use alpine::handshake::HandshakeError;
2use alpine::stream::StreamError;
3use thiserror::Error;
4
5use crate::transport::TransportError;
6
7#[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}