aimdb_core/remote/
error.rs

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