1use std::path::PathBuf;
6use thiserror::Error;
7
8#[derive(Error, Debug)]
10pub enum Mecha10Error {
11 #[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 #[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 #[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 #[error("Circuit breaker open for service '{service}': {message}")]
77 CircuitBreakerOpen { service: String, message: String },
78
79 #[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 #[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 #[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 #[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 #[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 #[error("{0}")]
159 Other(String),
160}
161
162pub type Result<T> = std::result::Result<T, Mecha10Error>;
164
165impl Mecha10Error {
170 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 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 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 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 pub fn configuration(message: impl Into<String>) -> Self {
230 Self::Configuration(message.into())
231 }
232
233 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 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
250impl From<anyhow::Error> for Mecha10Error {
255 fn from(err: anyhow::Error) -> Self {
256 Self::Other(err.to_string())
257 }
258}
259
260