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