1use std::fmt;
7
8#[derive(Debug)]
13pub enum Error {
14 NodeNotFound(crate::types::NodeId),
16
17 EdgeNotFound(crate::types::EdgeId),
19
20 PropertyNotFound(String),
22
23 LabelNotFound(String),
25
26 TypeMismatch {
28 expected: String,
30 found: String,
32 },
33
34 InvalidValue(String),
36
37 Transaction(TransactionError),
39
40 Storage(StorageError),
42
43 Query(QueryError),
45
46 Serialization(String),
48
49 Io(std::io::Error),
51
52 Internal(String),
54}
55
56impl fmt::Display for Error {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 match self {
59 Error::NodeNotFound(id) => write!(f, "Node not found: {id}"),
60 Error::EdgeNotFound(id) => write!(f, "Edge not found: {id}"),
61 Error::PropertyNotFound(key) => write!(f, "Property not found: {key}"),
62 Error::LabelNotFound(label) => write!(f, "Label not found: {label}"),
63 Error::TypeMismatch { expected, found } => {
64 write!(f, "Type mismatch: expected {expected}, found {found}")
65 }
66 Error::InvalidValue(msg) => write!(f, "Invalid value: {msg}"),
67 Error::Transaction(e) => write!(f, "Transaction error: {e}"),
68 Error::Storage(e) => write!(f, "Storage error: {e}"),
69 Error::Query(e) => write!(f, "Query error: {e}"),
70 Error::Serialization(msg) => write!(f, "Serialization error: {msg}"),
71 Error::Io(e) => write!(f, "I/O error: {e}"),
72 Error::Internal(msg) => write!(f, "Internal error: {msg}"),
73 }
74 }
75}
76
77impl std::error::Error for Error {
78 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
79 match self {
80 Error::Io(e) => Some(e),
81 Error::Transaction(e) => Some(e),
82 Error::Storage(e) => Some(e),
83 Error::Query(e) => Some(e),
84 _ => None,
85 }
86 }
87}
88
89impl From<std::io::Error> for Error {
90 fn from(e: std::io::Error) -> Self {
91 Error::Io(e)
92 }
93}
94
95#[derive(Debug, Clone)]
97pub enum TransactionError {
98 Aborted,
100
101 Conflict,
103
104 WriteConflict(String),
106
107 SerializationFailure(String),
112
113 Deadlock,
115
116 Timeout,
118
119 ReadOnly,
121
122 InvalidState(String),
124}
125
126impl fmt::Display for TransactionError {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 match self {
129 TransactionError::Aborted => write!(f, "Transaction aborted"),
130 TransactionError::Conflict => write!(f, "Transaction conflict"),
131 TransactionError::WriteConflict(msg) => write!(f, "Write conflict: {msg}"),
132 TransactionError::SerializationFailure(msg) => {
133 write!(f, "Serialization failure (SSI): {msg}")
134 }
135 TransactionError::Deadlock => write!(f, "Deadlock detected"),
136 TransactionError::Timeout => write!(f, "Transaction timeout"),
137 TransactionError::ReadOnly => write!(f, "Cannot write in read-only transaction"),
138 TransactionError::InvalidState(msg) => write!(f, "Invalid transaction state: {msg}"),
139 }
140 }
141}
142
143impl std::error::Error for TransactionError {}
144
145impl From<TransactionError> for Error {
146 fn from(e: TransactionError) -> Self {
147 Error::Transaction(e)
148 }
149}
150
151#[derive(Debug, Clone)]
153pub enum StorageError {
154 Corruption(String),
156
157 Full,
159
160 InvalidWalEntry(String),
162
163 RecoveryFailed(String),
165
166 CheckpointFailed(String),
168}
169
170impl fmt::Display for StorageError {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 match self {
173 StorageError::Corruption(msg) => write!(f, "Storage corruption: {msg}"),
174 StorageError::Full => write!(f, "Storage is full"),
175 StorageError::InvalidWalEntry(msg) => write!(f, "Invalid WAL entry: {msg}"),
176 StorageError::RecoveryFailed(msg) => write!(f, "Recovery failed: {msg}"),
177 StorageError::CheckpointFailed(msg) => write!(f, "Checkpoint failed: {msg}"),
178 }
179 }
180}
181
182impl std::error::Error for StorageError {}
183
184impl From<StorageError> for Error {
185 fn from(e: StorageError) -> Self {
186 Error::Storage(e)
187 }
188}
189
190#[derive(Debug, Clone)]
196pub struct QueryError {
197 pub kind: QueryErrorKind,
199 pub message: String,
201 pub span: Option<SourceSpan>,
203 pub source_query: Option<String>,
205 pub hint: Option<String>,
207}
208
209impl QueryError {
210 pub fn new(kind: QueryErrorKind, message: impl Into<String>) -> Self {
212 Self {
213 kind,
214 message: message.into(),
215 span: None,
216 source_query: None,
217 hint: None,
218 }
219 }
220
221 #[must_use]
223 pub fn with_span(mut self, span: SourceSpan) -> Self {
224 self.span = Some(span);
225 self
226 }
227
228 #[must_use]
230 pub fn with_source(mut self, query: impl Into<String>) -> Self {
231 self.source_query = Some(query.into());
232 self
233 }
234
235 #[must_use]
237 pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
238 self.hint = Some(hint.into());
239 self
240 }
241}
242
243impl fmt::Display for QueryError {
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245 write!(f, "{}: {}", self.kind, self.message)?;
246
247 if let (Some(span), Some(query)) = (&self.span, &self.source_query) {
248 write!(f, "\n --> query:{}:{}", span.line, span.column)?;
249
250 if let Some(line) = query.lines().nth(span.line.saturating_sub(1) as usize) {
252 write!(f, "\n |")?;
253 write!(f, "\n {} | {}", span.line, line)?;
254 write!(f, "\n | ")?;
255
256 for _ in 0..span.column.saturating_sub(1) {
258 write!(f, " ")?;
259 }
260 for _ in span.start..span.end {
261 write!(f, "^")?;
262 }
263 }
264 }
265
266 if let Some(hint) = &self.hint {
267 write!(f, "\n |\n help: {hint}")?;
268 }
269
270 Ok(())
271 }
272}
273
274impl std::error::Error for QueryError {}
275
276impl From<QueryError> for Error {
277 fn from(e: QueryError) -> Self {
278 Error::Query(e)
279 }
280}
281
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
284pub enum QueryErrorKind {
285 Lexer,
287 Syntax,
289 Semantic,
291 Optimization,
293 Execution,
295}
296
297impl fmt::Display for QueryErrorKind {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 match self {
300 QueryErrorKind::Lexer => write!(f, "lexer error"),
301 QueryErrorKind::Syntax => write!(f, "syntax error"),
302 QueryErrorKind::Semantic => write!(f, "semantic error"),
303 QueryErrorKind::Optimization => write!(f, "optimization error"),
304 QueryErrorKind::Execution => write!(f, "execution error"),
305 }
306 }
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
311pub struct SourceSpan {
312 pub start: usize,
314 pub end: usize,
316 pub line: u32,
318 pub column: u32,
320}
321
322impl SourceSpan {
323 pub const fn new(start: usize, end: usize, line: u32, column: u32) -> Self {
325 Self {
326 start,
327 end,
328 line,
329 column,
330 }
331 }
332}
333
334pub type Result<T> = std::result::Result<T, Error>;
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn test_error_display() {
343 let err = Error::NodeNotFound(crate::types::NodeId::new(42));
344 assert_eq!(err.to_string(), "Node not found: 42");
345
346 let err = Error::TypeMismatch {
347 expected: "INT64".to_string(),
348 found: "STRING".to_string(),
349 };
350 assert_eq!(
351 err.to_string(),
352 "Type mismatch: expected INT64, found STRING"
353 );
354 }
355
356 #[test]
357 fn test_query_error_formatting() {
358 let query = "MATCH (n:Peron) RETURN n";
359 let err = QueryError::new(QueryErrorKind::Semantic, "Unknown label 'Peron'")
360 .with_span(SourceSpan::new(9, 14, 1, 10))
361 .with_source(query)
362 .with_hint("Did you mean 'Person'?");
363
364 let msg = err.to_string();
365 assert!(msg.contains("Unknown label 'Peron'"));
366 assert!(msg.contains("query:1:10"));
367 assert!(msg.contains("Did you mean 'Person'?"));
368 }
369
370 #[test]
371 fn test_transaction_error() {
372 let err: Error = TransactionError::Conflict.into();
373 assert!(matches!(
374 err,
375 Error::Transaction(TransactionError::Conflict)
376 ));
377 }
378
379 #[test]
380 fn test_io_error_conversion() {
381 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
382 let err: Error = io_err.into();
383 assert!(matches!(err, Error::Io(_)));
384 }
385}