use alpine::handshake::HandshakeError;
use alpine::stream::StreamError;
use thiserror::Error;
use crate::transport::TransportError;
#[derive(Error, Debug)]
pub enum AlpineSdkError {
#[error("transport error: {0}")]
Transport(#[from] TransportError),
#[error("connection timed out")]
Timeout,
#[error("discovery failed: {0}")]
DiscoveryFailed(String),
#[error("invalid discovery reply")]
InvalidDiscoveryReply,
#[error("handshake already in progress")]
HandshakeAlreadyInProgress,
#[error("discovery not allowed once handshake has begun")]
DiscoveryAfterHandshake,
#[error("invalid phase transition: {0}")]
InvalidPhaseTransition(String),
#[error("missing client_nonce")]
MissingClientNonce,
#[error("handshake failed: {0}")]
HandshakeFailed(String),
#[error("device identity verification failed")]
IdentityVerificationFailed,
#[error("device identity not trusted: {0}")]
UntrustedDevice(String),
#[error("io error: {0}")]
Io(String),
#[error("no active session")]
NoActiveSession,
#[error("session expired")]
SessionExpired,
#[error("streaming not supported by device")]
StreamingNotSupported,
#[error("invalid channel value")]
InvalidChannelValue,
#[error("probe failed: {0}")]
ProbeFailed(String),
#[error("invalid capabilities: {0}")]
InvalidCapabilities(String),
#[error("dangerous control command blocked (enable allow_dangerous)")]
DangerousControlDisallowed,
#[error("sensitive operation requires trusted identity")]
SensitiveOperationRequiresTrust,
#[error("vendor extension not registered: {0}")]
VendorExtensionNotRegistered(String),
#[error("unsupported environment: {0}")]
UnsupportedEnvironment(String),
#[error("incompatible protocol: {0}")]
IncompatibleProtocol(String),
#[error("device quarantined: {0}")]
Quarantined(String),
#[error("status mismatch: {0}")]
StatusMismatch(String),
#[error("device selection denied: {0}")]
SelectionDenied(String),
#[error("internal error: {0}")]
Internal(String),
}
impl AlpineSdkError {
pub fn user_hint(&self) -> Option<&'static str> {
match self {
AlpineSdkError::Timeout => Some("request timed out; check network reachability"),
AlpineSdkError::DiscoveryFailed(_) => {
Some("discovery failed; confirm the device is on the same network")
}
AlpineSdkError::HandshakeFailed(_) => {
Some("handshake failed; verify credentials and device time")
}
AlpineSdkError::UntrustedDevice(_) => {
Some("device identity not trusted; verify trust bundle")
}
AlpineSdkError::ProbeFailed(_) => Some("device probe failed; verify device health"),
AlpineSdkError::DangerousControlDisallowed => {
Some("dangerous control blocked; enable allow_dangerous to proceed")
}
AlpineSdkError::SensitiveOperationRequiresTrust => {
Some("operation requires a trusted device identity")
}
AlpineSdkError::VendorExtensionNotRegistered(_) => {
Some("vendor extension not registered; register before use")
}
AlpineSdkError::UnsupportedEnvironment(_) => {
Some("environment not supported for UDP discovery/control")
}
AlpineSdkError::IncompatibleProtocol(_) => {
Some("device protocol version is incompatible with this SDK")
}
AlpineSdkError::Quarantined(_) => {
Some("device is quarantined; observe-only mode enforced")
}
AlpineSdkError::InvalidCapabilities(_) => {
Some("requested capabilities are not supported by the device")
}
AlpineSdkError::StatusMismatch(_) => {
Some("device does not support standard status; use vendor status helper")
}
AlpineSdkError::SelectionDenied(_) => Some("device rejected by selection policy"),
_ => None,
}
}
pub fn internal_cause(&self) -> Option<&str> {
match self {
AlpineSdkError::DiscoveryFailed(detail) => Some(detail.as_str()),
AlpineSdkError::HandshakeFailed(detail) => Some(detail.as_str()),
AlpineSdkError::UntrustedDevice(detail) => Some(detail.as_str()),
AlpineSdkError::Io(detail) => Some(detail.as_str()),
AlpineSdkError::ProbeFailed(detail) => Some(detail.as_str()),
AlpineSdkError::InvalidCapabilities(detail) => Some(detail.as_str()),
AlpineSdkError::VendorExtensionNotRegistered(detail) => Some(detail.as_str()),
AlpineSdkError::UnsupportedEnvironment(detail) => Some(detail.as_str()),
AlpineSdkError::IncompatibleProtocol(detail) => Some(detail.as_str()),
AlpineSdkError::Quarantined(detail) => Some(detail.as_str()),
AlpineSdkError::StatusMismatch(detail) => Some(detail.as_str()),
AlpineSdkError::SelectionDenied(detail) => Some(detail.as_str()),
AlpineSdkError::Internal(detail) => Some(detail.as_str()),
_ => None,
}
}
pub fn with_context(
self,
device_id: Option<String>,
ip: Option<String>,
port: Option<u16>,
operation: Option<String>,
) -> SdkErrorContext {
SdkErrorContext::with_context(self, device_id, ip, port, operation)
}
}
#[derive(Debug)]
pub struct SdkErrorContext {
pub error: AlpineSdkError,
pub device_id: Option<String>,
pub ip: Option<String>,
pub port: Option<u16>,
pub operation: Option<String>,
}
impl SdkErrorContext {
pub fn new(error: AlpineSdkError) -> Self {
Self {
error,
device_id: None,
ip: None,
port: None,
operation: None,
}
}
pub fn with_context(
error: AlpineSdkError,
device_id: Option<String>,
ip: Option<String>,
port: Option<u16>,
operation: Option<String>,
) -> Self {
Self {
error,
device_id,
ip,
port,
operation,
}
}
}
impl std::fmt::Display for SdkErrorContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.error)?;
if self.device_id.is_none()
&& self.ip.is_none()
&& self.port.is_none()
&& self.operation.is_none()
{
return Ok(());
}
write!(f, " (")?;
let mut first = true;
if let Some(device_id) = &self.device_id {
write!(f, "device_id={}", device_id)?;
first = false;
}
if let Some(ip) = &self.ip {
if !first {
write!(f, ", ")?;
}
write!(f, "ip={}", ip)?;
first = false;
}
if let Some(port) = self.port {
if !first {
write!(f, ", ")?;
}
write!(f, "port={}", port)?;
first = false;
}
if let Some(operation) = &self.operation {
if !first {
write!(f, ", ")?;
}
write!(f, "op={}", operation)?;
}
write!(f, ")")
}
}
impl std::error::Error for SdkErrorContext {}
impl From<HandshakeError> for AlpineSdkError {
fn from(err: HandshakeError) -> Self {
match err {
HandshakeError::Transport(_) => AlpineSdkError::HandshakeFailed(err.to_string()),
other => AlpineSdkError::HandshakeFailed(other.to_string()),
}
}
}
impl From<StreamError> for AlpineSdkError {
fn from(err: StreamError) -> Self {
AlpineSdkError::Internal(err.to_string())
}
}