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 _ => None,
82 }
83 }
84}
85
86impl From<std::io::Error> for Error {
87 fn from(e: std::io::Error) -> Self {
88 Error::Io(e)
89 }
90}
91
92#[derive(Debug, Clone)]
94pub enum TransactionError {
95 Aborted,
97
98 Conflict,
100
101 WriteConflict(String),
103
104 SerializationFailure(String),
109
110 Deadlock,
112
113 Timeout,
115
116 ReadOnly,
118
119 InvalidState(String),
121}
122
123impl fmt::Display for TransactionError {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 match self {
126 TransactionError::Aborted => write!(f, "Transaction aborted"),
127 TransactionError::Conflict => write!(f, "Transaction conflict"),
128 TransactionError::WriteConflict(msg) => write!(f, "Write conflict: {msg}"),
129 TransactionError::SerializationFailure(msg) => {
130 write!(f, "Serialization failure (SSI): {msg}")
131 }
132 TransactionError::Deadlock => write!(f, "Deadlock detected"),
133 TransactionError::Timeout => write!(f, "Transaction timeout"),
134 TransactionError::ReadOnly => write!(f, "Cannot write in read-only transaction"),
135 TransactionError::InvalidState(msg) => write!(f, "Invalid transaction state: {msg}"),
136 }
137 }
138}
139
140impl std::error::Error for TransactionError {}
141
142impl From<TransactionError> for Error {
143 fn from(e: TransactionError) -> Self {
144 Error::Transaction(e)
145 }
146}
147
148#[derive(Debug, Clone)]
150pub enum StorageError {
151 Corruption(String),
153
154 Full,
156
157 InvalidWalEntry(String),
159
160 RecoveryFailed(String),
162
163 CheckpointFailed(String),
165}
166
167impl fmt::Display for StorageError {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 match self {
170 StorageError::Corruption(msg) => write!(f, "Storage corruption: {msg}"),
171 StorageError::Full => write!(f, "Storage is full"),
172 StorageError::InvalidWalEntry(msg) => write!(f, "Invalid WAL entry: {msg}"),
173 StorageError::RecoveryFailed(msg) => write!(f, "Recovery failed: {msg}"),
174 StorageError::CheckpointFailed(msg) => write!(f, "Checkpoint failed: {msg}"),
175 }
176 }
177}
178
179impl std::error::Error for StorageError {}
180
181impl From<StorageError> for Error {
182 fn from(e: StorageError) -> Self {
183 Error::Storage(e)
184 }
185}
186
187#[derive(Debug, Clone)]
193pub struct QueryError {
194 pub kind: QueryErrorKind,
196 pub message: String,
198 pub span: Option<SourceSpan>,
200 pub source_query: Option<String>,
202 pub hint: Option<String>,
204}
205
206impl QueryError {
207 pub fn new(kind: QueryErrorKind, message: impl Into<String>) -> Self {
209 Self {
210 kind,
211 message: message.into(),
212 span: None,
213 source_query: None,
214 hint: None,
215 }
216 }
217
218 #[must_use]
220 pub fn with_span(mut self, span: SourceSpan) -> Self {
221 self.span = Some(span);
222 self
223 }
224
225 #[must_use]
227 pub fn with_source(mut self, query: impl Into<String>) -> Self {
228 self.source_query = Some(query.into());
229 self
230 }
231
232 #[must_use]
234 pub fn with_hint(mut self, hint: impl Into<String>) -> Self {
235 self.hint = Some(hint.into());
236 self
237 }
238}
239
240impl fmt::Display for QueryError {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 write!(f, "{}: {}", self.kind, self.message)?;
243
244 if let (Some(span), Some(query)) = (&self.span, &self.source_query) {
245 write!(f, "\n --> query:{}:{}", span.line, span.column)?;
246
247 if let Some(line) = query.lines().nth(span.line.saturating_sub(1) as usize) {
249 write!(f, "\n |")?;
250 write!(f, "\n {} | {}", span.line, line)?;
251 write!(f, "\n | ")?;
252
253 for _ in 0..span.column.saturating_sub(1) {
255 write!(f, " ")?;
256 }
257 for _ in span.start..span.end {
258 write!(f, "^")?;
259 }
260 }
261 }
262
263 if let Some(hint) = &self.hint {
264 write!(f, "\n |\n help: {hint}")?;
265 }
266
267 Ok(())
268 }
269}
270
271impl std::error::Error for QueryError {}
272
273impl From<QueryError> for Error {
274 fn from(e: QueryError) -> Self {
275 Error::Query(e)
276 }
277}
278
279#[derive(Debug, Clone, Copy, PartialEq, Eq)]
281pub enum QueryErrorKind {
282 Lexer,
284 Syntax,
286 Semantic,
288 Optimization,
290 Execution,
292}
293
294impl fmt::Display for QueryErrorKind {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 match self {
297 QueryErrorKind::Lexer => write!(f, "lexer error"),
298 QueryErrorKind::Syntax => write!(f, "syntax error"),
299 QueryErrorKind::Semantic => write!(f, "semantic error"),
300 QueryErrorKind::Optimization => write!(f, "optimization error"),
301 QueryErrorKind::Execution => write!(f, "execution error"),
302 }
303 }
304}
305
306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
308pub struct SourceSpan {
309 pub start: usize,
311 pub end: usize,
313 pub line: u32,
315 pub column: u32,
317}
318
319impl SourceSpan {
320 pub const fn new(start: usize, end: usize, line: u32, column: u32) -> Self {
322 Self {
323 start,
324 end,
325 line,
326 column,
327 }
328 }
329}
330
331pub type Result<T> = std::result::Result<T, Error>;
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[test]
339 fn test_error_display() {
340 let err = Error::NodeNotFound(crate::types::NodeId::new(42));
341 assert_eq!(err.to_string(), "Node not found: 42");
342
343 let err = Error::TypeMismatch {
344 expected: "INT64".to_string(),
345 found: "STRING".to_string(),
346 };
347 assert_eq!(
348 err.to_string(),
349 "Type mismatch: expected INT64, found STRING"
350 );
351 }
352
353 #[test]
354 fn test_query_error_formatting() {
355 let query = "MATCH (n:Peron) RETURN n";
356 let err = QueryError::new(QueryErrorKind::Semantic, "Unknown label 'Peron'")
357 .with_span(SourceSpan::new(9, 14, 1, 10))
358 .with_source(query)
359 .with_hint("Did you mean 'Person'?");
360
361 let msg = err.to_string();
362 assert!(msg.contains("Unknown label 'Peron'"));
363 assert!(msg.contains("query:1:10"));
364 assert!(msg.contains("Did you mean 'Person'?"));
365 }
366
367 #[test]
368 fn test_transaction_error() {
369 let err: Error = TransactionError::Conflict.into();
370 assert!(matches!(
371 err,
372 Error::Transaction(TransactionError::Conflict)
373 ));
374 }
375
376 #[test]
377 fn test_io_error_conversion() {
378 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
379 let err: Error = io_err.into();
380 assert!(matches!(err, Error::Io(_)));
381 }
382}