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