elizaos_plugin_nextcloud_talk/
error.rs1use std::fmt;
2use thiserror::Error;
3
4pub type Result<T> = std::result::Result<T, NextcloudTalkError>;
6
7#[derive(Debug, Error)]
8pub enum NextcloudTalkError {
10 #[error("Nextcloud Talk service not initialized - call start() first")]
11 ServiceNotInitialized,
13
14 #[error("Nextcloud Talk service is already running")]
15 AlreadyRunning,
17
18 #[error("Failed to connect to Nextcloud Talk: {0}")]
19 ConnectionFailed(String),
21
22 #[error("Nextcloud Talk API error: {0}")]
23 ApiError(String),
25
26 #[error("Configuration error: {0}")]
27 ConfigError(String),
29
30 #[error("Missing required setting: {0}")]
31 MissingSetting(String),
33
34 #[error("Invalid room token: {0}")]
35 InvalidRoomToken(String),
37
38 #[error("Invalid argument: {0}")]
39 InvalidArgument(String),
41
42 #[error("Message too long: {length} characters (max: {max})")]
43 MessageTooLong {
45 length: usize,
47 max: usize,
49 },
50
51 #[error("Room not found: {0}")]
52 RoomNotFound(String),
54
55 #[error("User not found: {0}")]
56 UserNotFound(String),
58
59 #[error("Permission denied: {0}")]
60 PermissionDenied(String),
62
63 #[error("Authentication failed: {0}")]
64 AuthenticationFailed(String),
66
67 #[error("Signature verification failed")]
68 SignatureVerificationFailed,
70
71 #[error("Rate limited by Nextcloud Talk API, retry after {retry_after_secs}s")]
72 RateLimited {
74 retry_after_secs: u64,
76 },
77
78 #[error("Operation timed out after {timeout_ms}ms")]
79 Timeout {
81 timeout_ms: u64,
83 },
84
85 #[error("Internal error: {0}")]
86 Internal(String),
88
89 #[error("Serialization error: {0}")]
90 SerializationError(String),
92
93 #[error("Action validation failed: {0}")]
94 ValidationFailed(String),
96
97 #[error("Webhook error: {0}")]
98 WebhookError(String),
100}
101
102impl NextcloudTalkError {
103 pub fn is_retryable(&self) -> bool {
105 matches!(
106 self,
107 NextcloudTalkError::RateLimited { .. }
108 | NextcloudTalkError::Timeout { .. }
109 | NextcloudTalkError::ConnectionFailed(_)
110 )
111 }
112
113 pub fn retry_after_secs(&self) -> Option<u64> {
115 match self {
116 NextcloudTalkError::RateLimited { retry_after_secs } => Some(*retry_after_secs),
117 NextcloudTalkError::Timeout { timeout_ms } => Some(*timeout_ms / 2000),
118 _ => None,
119 }
120 }
121}
122
123impl From<serde_json::Error> for NextcloudTalkError {
124 fn from(err: serde_json::Error) -> Self {
125 NextcloudTalkError::SerializationError(err.to_string())
126 }
127}
128
129impl From<std::io::Error> for NextcloudTalkError {
130 fn from(err: std::io::Error) -> Self {
131 NextcloudTalkError::Internal(format!("I/O error: {}", err))
132 }
133}
134
135impl From<reqwest::Error> for NextcloudTalkError {
136 fn from(err: reqwest::Error) -> Self {
137 NextcloudTalkError::ApiError(err.to_string())
138 }
139}
140
141#[derive(Debug)]
142pub struct ErrorContext<E: fmt::Display> {
144 error: E,
145 context: String,
146}
147
148impl<E: fmt::Display> fmt::Display for ErrorContext<E> {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 write!(f, "{}: {}", self.context, self.error)
151 }
152}
153
154impl<E: fmt::Display + fmt::Debug> std::error::Error for ErrorContext<E> {}
155
156pub trait WithContext<T, E: fmt::Display> {
158 fn with_context<F: FnOnce() -> String>(self, f: F) -> std::result::Result<T, ErrorContext<E>>;
160}
161
162impl<T, E: fmt::Display> WithContext<T, E> for std::result::Result<T, E> {
163 fn with_context<F: FnOnce() -> String>(self, f: F) -> std::result::Result<T, ErrorContext<E>> {
164 self.map_err(|e| ErrorContext {
165 error: e,
166 context: f(),
167 })
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_error_display() {
177 let err = NextcloudTalkError::MissingSetting("NEXTCLOUD_URL".to_string());
178 assert!(err.to_string().contains("NEXTCLOUD_URL"));
179 }
180
181 #[test]
182 fn test_error_retryable() {
183 assert!(NextcloudTalkError::RateLimited {
184 retry_after_secs: 10
185 }
186 .is_retryable());
187 assert!(NextcloudTalkError::Timeout { timeout_ms: 5000 }.is_retryable());
188 assert!(!NextcloudTalkError::ServiceNotInitialized.is_retryable());
189 }
190
191 #[test]
192 fn test_retry_after() {
193 let err = NextcloudTalkError::RateLimited {
194 retry_after_secs: 10,
195 };
196 assert_eq!(err.retry_after_secs(), Some(10));
197
198 let err = NextcloudTalkError::ServiceNotInitialized;
199 assert_eq!(err.retry_after_secs(), None);
200 }
201}