Skip to content

Commit 33ce3f3

Browse files
committed
refractor(errors): implement typed error system with error codes and thiserror
1 parent c3f3882 commit 33ce3f3

10 files changed

Lines changed: 413 additions & 207 deletions

File tree

nexum_core/src/bridge/error.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use thiserror::Error;
2+
3+
#[derive(Debug, Error)]
4+
pub enum BridgeError {
5+
#[error("Python bridge not initialized")]
6+
NotInitialized,
7+
8+
#[error("Python operation failed: {0}")]
9+
PythonError(String),
10+
11+
#[error("Failed to get current directory")]
12+
DirectoryError,
13+
14+
#[error("Invalid path")]
15+
InvalidaPath,
16+
}
17+
18+
pub type Result<T> = std::result::Result<T, BridgeError>;

nexum_core/src/bridge/mod.rs

Lines changed: 80 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
use anyhow::{anyhow, Result};
1+
//use anyhow::{anyhow};
2+
use pyo3::Py;
3+
use pyo3::types::PyAny;
24
use pyo3::prelude::*;
35
use pyo3::types::{PyList, PyModule};
6+
use crate::bridge::error::{BridgeError, Result};
7+
pub mod error;
48

59
pub struct PythonBridge {
610
initialized: bool,
@@ -12,7 +16,7 @@ impl PythonBridge {
1216
}
1317

1418
pub fn initialize(&mut self) -> Result<()> {
15-
Python::with_gil(|py| {
19+
Python::try_attach(|py| {
1620
let sys = py.import("sys")?;
1721
let path_attr = sys.getattr("path")?;
1822
let path = path_attr.downcast::<PyList>()?;
@@ -24,18 +28,20 @@ impl PythonBridge {
2428
path.insert(0, nexum_ai_path)?;
2529

2630
Ok::<(), PyErr>(())
27-
})?;
28-
31+
})
32+
//.ok_or(BridgeError::PythonError("Python interpreter not available.".into()))?;
33+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
34+
.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))?;
2935
self.initialized = true;
3036
Ok(())
3137
}
3238

3339
pub fn vectorize(&self, text: &str) -> Result<Vec<f32>> {
3440
if !self.initialized {
35-
return Err(anyhow!("Python bridge not initialized"));
41+
return Err(BridgeError::NotInitialized);
3642
}
3743

38-
Python::with_gil(|py| {
44+
Python::try_attach(|py| {
3945
let nexum_ai = PyModule::import(py, "nexum_ai.optimizer")?;
4046
let semantic_cache = nexum_ai.getattr("SemanticCache")?;
4147
let cache_instance = semantic_cache.call0()?;
@@ -46,24 +52,28 @@ impl PythonBridge {
4652

4753
Ok(vector)
4854
})
49-
.map_err(|e: PyErr| anyhow!("Python error: {}", e))
55+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
56+
.map_err(|e:PyErr| BridgeError::PythonError(e.to_string()))
57+
//.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
5058
}
5159

5260
pub fn test_integration(&self) -> Result<String> {
53-
Python::with_gil(|py| {
61+
Python::try_attach(|py| {
5462
let nexum_ai = PyModule::import(py, "nexum_ai.optimizer")?;
5563
let test_func = nexum_ai.getattr("test_vectorization")?;
5664
let result = test_func.call0()?;
5765
let result_str: String = result.str()?.extract()?;
5866
Ok(result_str)
5967
})
60-
.map_err(|e: PyErr| anyhow!("Python error: {}", e))
68+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
69+
.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
70+
//.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
6171
}
6272
}
6373

6474
pub struct SemanticCache {
6575
bridge: PythonBridge,
66-
cache: PyObject,
76+
cache: Py<PyAny>,
6777
}
6878

6979
impl SemanticCache {
@@ -75,18 +85,23 @@ impl SemanticCache {
7585
let mut bridge = PythonBridge::new()?;
7686
bridge.initialize()?;
7787

78-
let cache = Python::with_gil(|py| {
88+
let cache = Python::try_attach(|py| {
7989
let nexum_ai = PyModule::import(py, "nexum_ai.optimizer")?;
8090
let semantic_cache_class = nexum_ai.getattr("SemanticCache")?;
8191
let cache_instance = semantic_cache_class.call1((0.95, cache_file))?;
82-
Ok::<PyObject, PyErr>(cache_instance.unbind())
83-
})?;
92+
Ok::<Py<PyAny>, PyErr>(cache_instance.unbind())
93+
})
94+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
95+
.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))?;
8496

85-
Ok(Self { bridge, cache })
97+
Ok(Self {
98+
bridge,
99+
cache,
100+
})
86101
}
87102

88103
pub fn get(&self, query: &str) -> Result<Option<String>> {
89-
Python::with_gil(|py| {
104+
Python::try_attach(|py| {
90105
let cache_bound = self.cache.bind(py);
91106
let result = cache_bound.call_method1("get", (query,))?;
92107

@@ -97,86 +112,103 @@ impl SemanticCache {
97112
Ok(Some(value))
98113
}
99114
})
100-
.map_err(|e: PyErr| anyhow!("Python error: {}", e))
115+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
116+
.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
117+
//.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
101118
}
102119

103120
pub fn put(&self, query: &str, result: &str) -> Result<()> {
104-
Python::with_gil(|py| {
121+
Python::try_attach(|py| {
105122
let cache_bound = self.cache.bind(py);
106123
cache_bound.call_method1("put", (query, result))?;
107124
Ok::<(), PyErr>(())
108125
})
109-
.map_err(|e: PyErr| anyhow!("Python error: {}", e))
126+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
127+
.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
128+
//.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
110129
}
111130

112131
pub fn vectorize(&self, text: &str) -> Result<Vec<f32>> {
113132
self.bridge.vectorize(text)
114133
}
115134

116135
pub fn save_cache(&self) -> Result<()> {
117-
Python::with_gil(|py| {
136+
Python::try_attach(|py| {
118137
let cache_bound = self.cache.bind(py);
119138
cache_bound.call_method0("save_cache")?;
120139
Ok::<(), PyErr>(())
121140
})
122-
.map_err(|e: PyErr| anyhow!("Python error: {}", e))
141+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
142+
.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
143+
//.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
123144
}
124145

125146
pub fn load_cache(&self) -> Result<()> {
126-
Python::with_gil(|py| {
147+
Python::try_attach(|py| {
127148
let cache_bound = self.cache.bind(py);
128149
cache_bound.call_method0("load_cache")?;
129150
Ok::<(), PyErr>(())
130151
})
131-
.map_err(|e: PyErr| anyhow!("Python error: {}", e))
152+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
153+
.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
154+
//.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
132155
}
133156

134157
pub fn clear_cache(&self) -> Result<()> {
135-
Python::with_gil(|py| {
158+
Python::try_attach(|py| {
136159
let cache_bound = self.cache.bind(py);
137160
cache_bound.call_method0("clear")?;
138161
Ok::<(), PyErr>(())
139162
})
140-
.map_err(|e: PyErr| anyhow!("Python error: {}", e))
163+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
164+
.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
165+
//.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
141166
}
142167

143168
pub fn get_cache_stats(&self) -> Result<String> {
144-
Python::with_gil(|py| {
169+
Python::try_attach(|py| {
145170
let cache_bound = self.cache.bind(py);
146171
let result = cache_bound.call_method0("get_cache_stats")?;
147172
let stats_str: String = result.str()?.extract()?;
148173
Ok(stats_str)
149174
})
150-
.map_err(|e: PyErr| anyhow!("Python error: {}", e))
175+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
176+
.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
177+
//.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
151178
}
152179

153180
pub fn explain_query(&self, query: &str) -> Result<String> {
154-
Python::with_gil(|py| {
181+
Python::try_attach(|py| {
155182
let cache_bound = self.cache.bind(py);
156183
let result = cache_bound.call_method1("explain_query", (query,))?;
157184
let explain_str: String = result.str()?.extract()?;
158185
Ok(explain_str)
159186
})
160-
.map_err(|e: PyErr| anyhow!("Python error: {}", e))
187+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
188+
.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
189+
//.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
161190
}
162191
}
163192

164193
pub struct NLTranslator {
165194
_bridge: PythonBridge,
166-
translator: PyObject,
195+
translator: Py<PyAny>,
167196
}
168197

169198
impl NLTranslator {
170199
pub fn new() -> Result<Self> {
171200
let mut bridge = PythonBridge::new()?;
172201
bridge.initialize()?;
173202

174-
let translator = Python::with_gil(|py| {
203+
let translator = Python::try_attach(|py| {
175204
let nexum_ai = PyModule::import(py, "nexum_ai.translator")?;
176205
let translator_class = nexum_ai.getattr("NLTranslator")?;
177206
let translator_instance = translator_class.call0()?;
178-
Ok::<PyObject, PyErr>(translator_instance.unbind())
179-
})?;
207+
Ok::<Py<PyAny>, PyErr>(translator_instance.unbind())
208+
})
209+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
210+
.map_err(|e| BridgeError::PythonError(e.to_string()))?;
211+
180212

181213
Ok(Self {
182214
_bridge: bridge,
@@ -185,14 +217,16 @@ impl NLTranslator {
185217
}
186218

187219
pub fn translate(&self, natural_query: &str, schema: &str) -> Result<String> {
188-
Python::with_gil(|py| {
220+
Python::try_attach(|py| {
189221
let translator_bound = self.translator.bind(py);
190222
let result = translator_bound.call_method1("translate", (natural_query, schema))?;
191223

192224
let sql: String = result.extract()?;
193225
Ok(sql)
194226
})
195-
.map_err(|e: PyErr| anyhow!("Python error: {}", e))
227+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
228+
.map_err(|e:PyErr| BridgeError::PythonError(e.to_string()))
229+
196230
}
197231
}
198232

@@ -208,7 +242,7 @@ impl QueryExplainer {
208242
}
209243

210244
pub fn explain(&self, query: &str) -> Result<String> {
211-
Python::with_gil(|py| {
245+
Python::try_attach(|py| {
212246
let nexum_ai = PyModule::import(py, "nexum_ai.optimizer")?;
213247
let explain_func = nexum_ai.getattr("explain_query_plan")?;
214248
let format_func = nexum_ai.getattr("format_explain_output")?;
@@ -218,19 +252,22 @@ impl QueryExplainer {
218252
let output: String = formatted.extract()?;
219253
Ok(output)
220254
})
221-
.map_err(|e: PyErr| anyhow!("Python error: {}", e))
255+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
256+
.map_err(|e:PyErr| BridgeError::PythonError(e.to_string()))
222257
}
223258

224259
pub fn explain_raw(&self, query: &str) -> Result<String> {
225-
Python::with_gil(|py| {
260+
Python::try_attach(|py| {
226261
let nexum_ai = PyModule::import(py, "nexum_ai.optimizer")?;
227262
let explain_func = nexum_ai.getattr("explain_query_plan")?;
228263

229264
let result = explain_func.call1((query,))?;
230265
let output: String = result.str()?.extract()?;
231266
Ok(output)
232267
})
233-
.map_err(|e: PyErr| anyhow!("Python error: {}", e))
268+
.ok_or_else(|| BridgeError::PythonError("Python interpreter not available".into()))?
269+
.map_err(|e:PyErr| BridgeError::PythonError(e.to_string()))
270+
//.map_err(|e: PyErr| BridgeError::PythonError(e.to_string()))
234271
}
235272
}
236273

@@ -241,11 +278,15 @@ mod tests {
241278
fn check_python_available() -> bool {
242279
let mut bridge = PythonBridge::new().unwrap();
243280
bridge.initialize().is_ok()
244-
&& Python::with_gil(|py| PyModule::import(py, "nexum_ai.optimizer").is_ok())
281+
&& Python::try_attach(|py| PyModule::import(py, "nexum_ai.optimizer").is_ok()).unwrap_or(false)
245282
}
246283

247284
#[test]
248285
fn test_python_bridge_initialization() {
286+
if !check_python_available() {
287+
println!("Skipping test: Python environment not available");
288+
return;
289+
}
249290
let mut bridge = PythonBridge::new().unwrap();
250291
bridge.initialize().unwrap();
251292
assert!(bridge.initialized);

nexum_core/src/catalog/mod.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::sql::types::{Column, TableSchema};
22
use crate::storage::{Result, StorageEngine, StorageError};
3+
use crate::storage::error::ErrorCode;
34
use serde::{Deserialize, Serialize};
45

56
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -23,10 +24,14 @@ impl Catalog {
2324
let key = Self::table_key(name);
2425

2526
if self.storage.get(&key)?.is_some() {
26-
return Err(StorageError::WriteError(format!(
27+
return Err(StorageError::WriteError {
28+
code: ErrorCode::NxmStor103,
29+
reason:
30+
format!(
2731
"Table {} already exists",
28-
name
29-
)));
32+
name),
33+
suggestion: "Use a different table name".to_string(),
34+
});
3035
}
3136

3237
let cols: Vec<(String, String)> = columns

0 commit comments

Comments
 (0)