Skip to main content

peat_protocol/
error.rs

1//! Error types for the Peat protocol
2//!
3//! This module provides a comprehensive error hierarchy with context,
4//! recovery strategies, and structured error information.
5
6use thiserror::Error;
7
8/// Result type alias for Peat protocol operations
9pub type Result<T> = std::result::Result<T, Error>;
10
11/// Errors that can occur in the Peat protocol
12#[derive(Error, Debug)]
13pub enum Error {
14    /// Discovery phase errors
15    #[error("Discovery error: {message}")]
16    Discovery {
17        message: String,
18        #[source]
19        source: Option<Box<dyn std::error::Error + Send + Sync>>,
20    },
21
22    /// Cell formation errors
23    #[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    /// Hierarchical operation errors
32    #[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    /// Capability composition errors
41    #[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    /// CRDT/Storage errors
50    #[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    /// Network errors
60    #[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    /// Serialization/deserialization errors
69    #[error("Serialization error: {0}")]
70    Serialization(#[from] serde_json::Error),
71
72    /// Invalid state transition
73    #[error("Invalid state transition from {from} to {to}: {reason}")]
74    InvalidTransition {
75        from: String,
76        to: String,
77        reason: String,
78    },
79
80    /// Resource not found
81    #[error("Resource not found: {resource_type} with id {id}")]
82    NotFound { resource_type: String, id: String },
83
84    /// Configuration errors
85    #[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    /// Timeout errors
94    #[error("Operation timed out after {duration_ms}ms: {operation}")]
95    Timeout { operation: String, duration_ms: u64 },
96
97    /// Ditto-specific errors
98    #[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    /// Generic internal error
107    #[error("Internal error: {0}")]
108    Internal(String),
109
110    /// Invalid input provided
111    #[error("Invalid input: {0}")]
112    InvalidInput(String),
113
114    /// Command conflict detected
115    #[error("Command conflict detected: {0}")]
116    ConflictDetected(String),
117
118    /// Security/Authentication errors
119    #[error("Security error: {0}")]
120    Security(String),
121
122    /// Event operation errors (ADR-027)
123    #[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    /// Check if this error is recoverable
134    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    /// Get error severity level
146    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    /// Get context information from the error
159    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/// Error severity levels
192#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193pub enum ErrorSeverity {
194    /// Critical errors that require immediate attention
195    Critical,
196    /// Standard errors that prevent operation completion
197    Error,
198    /// Warnings about recoverable issues
199    Warning,
200    /// Informational messages about error conditions
201    Info,
202}
203
204/// Contextual information about an error
205#[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
221/// Helper functions for creating common errors
222impl Error {
223    /// Create a storage error with context
224    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    /// Create a network error with context
238    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    /// Create a timeout error with context
247    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    /// Create a configuration error with context
255    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;