Skip to main content

aimdb_core/remote/
error.rs

1//! Error types for AimX remote access protocol
2
3use alloc::format;
4use alloc::string::{String, ToString};
5use thiserror::Error;
6
7/// Error type for remote access operations
8#[derive(Debug, Clone, Error)]
9pub enum RemoteError {
10    /// Malformed message or invalid JSON
11    #[error("Protocol error: {message}")]
12    ProtocolError { message: String },
13
14    /// Incompatible protocol versions
15    #[error("Version mismatch: client {client_version}, server {server_version}")]
16    VersionMismatch {
17        client_version: String,
18        server_version: String,
19    },
20
21    /// Record or subscription not found
22    #[error("Not found: {resource}")]
23    NotFound { resource: String },
24
25    /// Operation not permitted
26    #[error("Permission denied: {reason}")]
27    PermissionDenied { reason: String },
28
29    /// Subscription queue overflow
30    #[error("Queue full: {queue_name}")]
31    QueueFull { queue_name: String },
32
33    /// Server internal error
34    #[error("Internal error: {message}")]
35    InternalError { message: String },
36
37    /// Too many subscriptions for this client
38    #[error("Too many subscriptions (limit: {limit})")]
39    TooManySubscriptions { limit: usize },
40
41    /// Record has no current value
42    #[error("No value: {record_name}")]
43    NoValue { record_name: String },
44
45    /// Record has no buffer configured
46    #[error("No buffer: {record_name}")]
47    NoBuffer { record_name: String },
48
49    /// Invalid parameter or value
50    #[error("Validation error: {message}")]
51    ValidationError { message: String },
52
53    /// Authentication token required
54    #[error("Authentication required")]
55    AuthRequired,
56
57    /// Invalid authentication token
58    #[error("Authentication failed")]
59    AuthFailed,
60}
61
62impl RemoteError {
63    /// Returns the protocol error code
64    pub fn code(&self) -> &'static str {
65        match self {
66            Self::ProtocolError { .. } => "PROTOCOL_ERROR",
67            Self::VersionMismatch { .. } => "VERSION_MISMATCH",
68            Self::NotFound { .. } => "NOT_FOUND",
69            Self::PermissionDenied { .. } => "PERMISSION_DENIED",
70            Self::QueueFull { .. } => "QUEUE_FULL",
71            Self::InternalError { .. } => "INTERNAL_ERROR",
72            Self::TooManySubscriptions { .. } => "TOO_MANY_SUBSCRIPTIONS",
73            Self::NoValue { .. } => "NO_VALUE",
74            Self::NoBuffer { .. } => "NO_BUFFER",
75            Self::ValidationError { .. } => "VALIDATION_ERROR",
76            Self::AuthRequired => "AUTH_REQUIRED",
77            Self::AuthFailed => "AUTH_FAILED",
78        }
79    }
80
81    /// Returns whether this error is retryable
82    pub fn is_retryable(&self) -> bool {
83        matches!(
84            self,
85            Self::NotFound { .. }
86                | Self::QueueFull { .. }
87                | Self::InternalError { .. }
88                | Self::NoValue { .. }
89        )
90    }
91}
92
93/// Result type for remote operations
94pub type RemoteResult<T> = Result<T, RemoteError>;
95
96// Conversion from DbError to RemoteError
97impl From<crate::DbError> for RemoteError {
98    fn from(err: crate::DbError) -> Self {
99        use crate::DbError;
100        let message = err.to_string();
101        match err {
102            DbError::RecordNotFound { .. } | DbError::RecordKeyNotFound { .. } => {
103                RemoteError::NotFound { resource: message }
104            }
105            DbError::InvalidOperation { .. } => RemoteError::ValidationError { message },
106            DbError::BufferFull { .. } => RemoteError::QueueFull {
107                queue_name: message,
108            },
109            DbError::PermissionDenied { .. } => RemoteError::PermissionDenied { reason: message },
110            _ => RemoteError::InternalError { message },
111        }
112    }
113}
114
115#[cfg(feature = "std")]
116impl From<std::io::Error> for RemoteError {
117    fn from(err: std::io::Error) -> Self {
118        RemoteError::InternalError {
119            message: format!("I/O error: {}", err),
120        }
121    }
122}
123
124impl From<serde_json::Error> for RemoteError {
125    fn from(err: serde_json::Error) -> Self {
126        RemoteError::ProtocolError {
127            message: format!("JSON error: {}", err),
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_error_codes() {
138        assert_eq!(
139            RemoteError::ProtocolError {
140                message: "test".to_string(),
141            }
142            .code(),
143            "PROTOCOL_ERROR"
144        );
145
146        assert_eq!(
147            RemoteError::NotFound {
148                resource: "test".to_string(),
149            }
150            .code(),
151            "NOT_FOUND"
152        );
153
154        assert_eq!(
155            RemoteError::PermissionDenied {
156                reason: "test".to_string(),
157            }
158            .code(),
159            "PERMISSION_DENIED"
160        );
161    }
162
163    #[test]
164    fn test_retryable() {
165        assert!(RemoteError::NotFound {
166            resource: "test".to_string(),
167        }
168        .is_retryable());
169
170        assert!(!RemoteError::PermissionDenied {
171            reason: "test".to_string(),
172        }
173        .is_retryable());
174    }
175}