1use thiserror::Error;
7
8pub type Result<T> = std::result::Result<T, Error>;
10
11#[derive(Error, Debug)]
13pub enum Error {
14 #[error("Discovery error: {message}")]
16 Discovery {
17 message: String,
18 #[source]
19 source: Option<Box<dyn std::error::Error + Send + Sync>>,
20 },
21
22 #[error("Cell formation error: {message}")]
24 SquadFormation {
25 message: String,
26 squad_id: Option<String>,
27 #[source]
28 source: Option<Box<dyn std::error::Error + Send + Sync>>,
29 },
30
31 #[error("Hierarchical operation error: {message}")]
33 HierarchicalOp {
34 message: String,
35 operation: String,
36 #[source]
37 source: Option<Box<dyn std::error::Error + Send + Sync>>,
38 },
39
40 #[error("Capability composition error: {message}")]
42 Composition {
43 message: String,
44 capability: Option<String>,
45 #[source]
46 source: Option<Box<dyn std::error::Error + Send + Sync>>,
47 },
48
49 #[error("Storage error: {message}")]
51 Storage {
52 message: String,
53 operation: Option<String>,
54 key: Option<String>,
55 #[source]
56 source: Option<Box<dyn std::error::Error + Send + Sync>>,
57 },
58
59 #[error("Network error: {message}")]
61 Network {
62 message: String,
63 peer_id: Option<String>,
64 #[source]
65 source: Option<Box<dyn std::error::Error + Send + Sync>>,
66 },
67
68 #[error("Serialization error: {0}")]
70 Serialization(#[from] serde_json::Error),
71
72 #[error("Invalid state transition from {from} to {to}: {reason}")]
74 InvalidTransition {
75 from: String,
76 to: String,
77 reason: String,
78 },
79
80 #[error("Resource not found: {resource_type} with id {id}")]
82 NotFound { resource_type: String, id: String },
83
84 #[error("Configuration error: {message}")]
86 Configuration {
87 message: String,
88 config_key: Option<String>,
89 #[source]
90 source: Option<Box<dyn std::error::Error + Send + Sync>>,
91 },
92
93 #[error("Operation timed out after {duration_ms}ms: {operation}")]
95 Timeout { operation: String, duration_ms: u64 },
96
97 #[error("Ditto error: {message}")]
99 Ditto {
100 message: String,
101 operation: Option<String>,
102 #[source]
103 source: Option<Box<dyn std::error::Error + Send + Sync>>,
104 },
105
106 #[error("Internal error: {0}")]
108 Internal(String),
109
110 #[error("Invalid input: {0}")]
112 InvalidInput(String),
113
114 #[error("Command conflict detected: {0}")]
116 ConflictDetected(String),
117
118 #[error("Security error: {0}")]
120 Security(String),
121
122 #[error("Event operation error: {message}")]
124 EventOp {
125 message: String,
126 operation: String,
127 #[source]
128 source: Option<Box<dyn std::error::Error + Send + Sync>>,
129 },
130}
131
132impl Error {
133 pub fn is_recoverable(&self) -> bool {
135 match self {
136 Error::Timeout { .. } | Error::Network { .. } => true,
137 Error::Storage {
138 operation: Some(op),
139 ..
140 } => op == "query" || op == "retrieve",
141 _ => false,
142 }
143 }
144
145 pub fn severity(&self) -> ErrorSeverity {
147 match self {
148 Error::Internal(_) => ErrorSeverity::Critical,
149 Error::Configuration { .. } => ErrorSeverity::Critical,
150 Error::InvalidTransition { .. } => ErrorSeverity::Error,
151 Error::Timeout { .. } => ErrorSeverity::Warning,
152 Error::Network { .. } => ErrorSeverity::Warning,
153 Error::NotFound { .. } => ErrorSeverity::Info,
154 _ => ErrorSeverity::Error,
155 }
156 }
157
158 pub fn context(&self) -> ErrorContext {
160 match self {
161 Error::Storage { key, operation, .. } => ErrorContext {
162 key: key.clone(),
163 operation: operation.clone(),
164 ..Default::default()
165 },
166 Error::Network { peer_id, .. } => ErrorContext {
167 peer_id: peer_id.clone(),
168 ..Default::default()
169 },
170 Error::SquadFormation { squad_id, .. } => ErrorContext {
171 squad_id: squad_id.clone(),
172 ..Default::default()
173 },
174 Error::Composition { capability, .. } => ErrorContext {
175 capability: capability.clone(),
176 ..Default::default()
177 },
178 Error::Timeout {
179 operation,
180 duration_ms,
181 } => ErrorContext {
182 operation: Some(operation.clone()),
183 duration_ms: Some(*duration_ms),
184 ..Default::default()
185 },
186 _ => ErrorContext::default(),
187 }
188 }
189}
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub enum ErrorSeverity {
194 Critical,
196 Error,
198 Warning,
200 Info,
202}
203
204#[derive(Debug, Clone, Default)]
206pub struct ErrorContext {
207 pub key: Option<String>,
208 pub operation: Option<String>,
209 pub peer_id: Option<String>,
210 pub squad_id: Option<String>,
211 pub capability: Option<String>,
212 pub duration_ms: Option<u64>,
213}
214
215impl From<anyhow::Error> for Error {
216 fn from(err: anyhow::Error) -> Self {
217 Error::Internal(err.to_string())
218 }
219}
220
221impl Error {
223 pub fn storage_error(
225 message: impl Into<String>,
226 operation: impl Into<String>,
227 key: Option<String>,
228 ) -> Self {
229 Error::Storage {
230 message: message.into(),
231 operation: Some(operation.into()),
232 key,
233 source: None,
234 }
235 }
236
237 pub fn network_error(message: impl Into<String>, peer_id: Option<String>) -> Self {
239 Error::Network {
240 message: message.into(),
241 peer_id,
242 source: None,
243 }
244 }
245
246 pub fn timeout_error(operation: impl Into<String>, duration_ms: u64) -> Self {
248 Error::Timeout {
249 operation: operation.into(),
250 duration_ms,
251 }
252 }
253
254 pub fn config_error(message: impl Into<String>, config_key: Option<String>) -> Self {
256 Error::Configuration {
257 message: message.into(),
258 config_key,
259 source: None,
260 }
261 }
262}
263
264#[cfg(test)]
265mod tests;