mecha10_core/
error.rs

1// Mecha10 Error Types
2//
3// Provides structured error handling with clear error messages and actionable suggestions.
4
5use std::path::PathBuf;
6use thiserror::Error;
7
8/// Main error type for the Mecha10 framework
9#[derive(Error, Debug)]
10pub enum Mecha10Error {
11    // ========================================================================
12    // Node Errors
13    // ========================================================================
14    #[error("Node initialization failed: {0}")]
15    NodeInit(String),
16
17    #[error("Node '{node_name}' failed: {message}")]
18    NodeFailed { node_name: String, message: String },
19
20    #[error("Node '{0}' not found")]
21    NodeNotFound(String),
22
23    // ========================================================================
24    // Configuration Errors
25    // ========================================================================
26    #[error("Configuration error in field '{field}': {message}\n  Expected: {expected}\n  Got: {got}\n  Suggestion: {suggestion}")]
27    ConfigValidation {
28        field: String,
29        message: String,
30        expected: String,
31        got: String,
32        suggestion: String,
33    },
34
35    #[error(
36        "Configuration file not found: {path}\n  Suggestion: Create the file or set environment variable {env_var}"
37    )]
38    ConfigNotFound { path: PathBuf, env_var: String },
39
40    #[error("Invalid configuration format: {0}\n  Suggestion: Validate against schema at {1}")]
41    ConfigInvalid(String, String),
42
43    #[error("Configuration error: {0}")]
44    Configuration(String),
45
46    // ========================================================================
47    // Messaging Errors
48    // ========================================================================
49    #[error("{message}\n  Suggestion: {suggestion}")]
50    MessagingError { message: String, suggestion: String },
51    #[error("Failed to connect to message broker at {url}\n  → Tried: {url}\n  → Suggestion: {suggestion}")]
52    MessagingConnectionFailed { url: String, suggestion: String },
53
54    #[error("Failed to publish message to topic '{topic}': {reason}")]
55    PublishFailed { topic: String, reason: String },
56
57    #[error("Failed to subscribe to topic '{topic}': {reason}")]
58    SubscribeFailed { topic: String, reason: String },
59
60    #[error("Message deserialization failed for topic '{topic}'\n  Expected type: {expected_type}\n  Error: {error}")]
61    DeserializationFailed {
62        topic: String,
63        expected_type: String,
64        error: String,
65    },
66
67    #[error("Message serialization failed: {0}")]
68    SerializationFailed(String),
69
70    #[error("Serialization error: {message}\n  Suggestion: {suggestion}")]
71    SerializationError { message: String, suggestion: String },
72
73    // ========================================================================
74    // Circuit Breaker Errors
75    // ========================================================================
76    #[error("Circuit breaker open for service '{service}': {message}")]
77    CircuitBreakerOpen { service: String, message: String },
78
79    // ========================================================================
80    // Model Errors
81    // ========================================================================
82    #[error("Failed to load model from '{uri}': {reason}\n  Suggestion: {suggestion}")]
83    ModelLoadFailed {
84        uri: String,
85        reason: String,
86        suggestion: String,
87    },
88
89    #[error("Model inference failed: {0}")]
90    InferenceFailed(String),
91
92    #[error(
93        "Model not found in MLflow registry: {model_name}/{version}\n  Suggestion: Check MLflow UI at {mlflow_url}"
94    )]
95    ModelNotFound {
96        model_name: String,
97        version: String,
98        mlflow_url: String,
99    },
100
101    #[error("Model checksum mismatch\n  Expected: {expected}\n  Got: {got}\n  Suggestion: Re-download the model")]
102    ModelChecksumMismatch { expected: String, got: String },
103
104    // ========================================================================
105    // Build Errors
106    // ========================================================================
107    #[error("Build failed for node '{node_name}':\n{stderr}\n  Suggestion: Check the error messages above")]
108    BuildFailed { node_name: String, stderr: String },
109
110    #[error("Hot-reload failed: {0}")]
111    HotReloadFailed(String),
112
113    // ========================================================================
114    // Runtime Errors
115    // ========================================================================
116    #[error("Runtime error: {0}")]
117    Runtime(String),
118
119    #[error("Timeout after {timeout_ms}ms waiting for: {operation}")]
120    Timeout { timeout_ms: u64, operation: String },
121
122    #[error("Shutdown error: {0}")]
123    Shutdown(String),
124
125    // ========================================================================
126    // IO Errors
127    // ========================================================================
128    #[error("File not found: {path}\n  Suggestion: {suggestion}")]
129    FileNotFound { path: PathBuf, suggestion: String },
130
131    #[error("IO error: {0}")]
132    Io(#[from] std::io::Error),
133
134    #[error("IO error: {message}\n  Suggestion: {suggestion}")]
135    IoError { message: String, suggestion: String },
136
137    // ========================================================================
138    // External Errors
139    // ========================================================================
140    #[error("JSON error: {0}")]
141    Json(#[from] serde_json::Error),
142
143    #[error("YAML error: {0}")]
144    Yaml(String),
145
146    #[error("Redis error: {0}")]
147    Redis(String),
148
149    #[error("Database error: {0}")]
150    Database(String),
151
152    #[error("HTTP error: {0}")]
153    Http(String),
154
155    // ========================================================================
156    // Generic Errors
157    // ========================================================================
158    #[error("{0}")]
159    Other(String),
160}
161
162/// Result type using Mecha10Error
163pub type Result<T> = std::result::Result<T, Mecha10Error>;
164
165// ============================================================================
166// Helper methods for creating errors with context
167// ============================================================================
168
169impl Mecha10Error {
170    /// Create a connection error with helpful suggestions
171    pub fn connection_failed(url: impl Into<String>) -> Self {
172        let url = url.into();
173
174        let suggestion = if url.contains("localhost") || url.contains("127.0.0.1") {
175            "Start Redis with `redis-server`\n  → Or set REDIS_URL environment variable".to_string()
176        } else {
177            format!("Check network connection and ensure Redis is running at {}", url)
178        };
179
180        Self::MessagingConnectionFailed { url, suggestion }
181    }
182
183    /// Create a config validation error with clear expectations
184    pub fn config_validation(
185        field: impl Into<String>,
186        message: impl Into<String>,
187        expected: impl Into<String>,
188        got: impl Into<String>,
189        suggestion: impl Into<String>,
190    ) -> Self {
191        Self::ConfigValidation {
192            field: field.into(),
193            message: message.into(),
194            expected: expected.into(),
195            got: got.into(),
196            suggestion: suggestion.into(),
197        }
198    }
199
200    /// Create a model load error with helpful suggestions
201    pub fn model_load_failed(uri: impl Into<String>, reason: impl Into<String>) -> Self {
202        let uri_str = uri.into();
203        let reason = reason.into();
204
205        let suggestion = if uri_str.starts_with("mlflow://") {
206            "Check MLflow server is running with `mecha10 mlflow start`\n  → Or verify MLFLOW_TRACKING_URI environment variable".to_string()
207        } else if uri_str.starts_with("http://") || uri_str.starts_with("https://") {
208            "Check network connection and model URL".to_string()
209        } else {
210            format!("Check file exists: {}", uri_str)
211        };
212
213        Self::ModelLoadFailed {
214            uri: uri_str,
215            reason,
216            suggestion,
217        }
218    }
219
220    /// Create a messaging error with a helpful suggestion
221    pub fn messaging_error(message: impl Into<String>, suggestion: impl Into<String>) -> Self {
222        Self::MessagingError {
223            message: message.into(),
224            suggestion: suggestion.into(),
225        }
226    }
227
228    /// Create a configuration error with context
229    pub fn configuration(message: impl Into<String>) -> Self {
230        Self::Configuration(message.into())
231    }
232
233    /// Create a file not found error with a helpful suggestion
234    pub fn file_not_found(path: impl Into<PathBuf>, suggestion: impl Into<String>) -> Self {
235        Self::FileNotFound {
236            path: path.into(),
237            suggestion: suggestion.into(),
238        }
239    }
240
241    /// Create a config not found error
242    pub fn config_not_found(path: impl Into<PathBuf>, env_var: impl Into<String>) -> Self {
243        Self::ConfigNotFound {
244            path: path.into(),
245            env_var: env_var.into(),
246        }
247    }
248}
249
250// ============================================================================
251// Conversion from anyhow::Error for compatibility
252// ============================================================================
253
254impl From<anyhow::Error> for Mecha10Error {
255    fn from(err: anyhow::Error) -> Self {
256        Self::Other(err.to_string())
257    }
258}
259
260// ============================================================================
261// Display formatting for better error messages
262// ============================================================================