Skip to main content

cypherlite_core/
error.rs

1// CypherLiteError definitions
2
3/// All errors that can occur in CypherLite operations.
4#[derive(thiserror::Error, Debug)]
5pub enum CypherLiteError {
6    /// Wrapper for standard I/O errors.
7    #[error("I/O error: {0}")]
8    IoError(#[from] std::io::Error),
9
10    /// A database page failed integrity checks.
11    #[error("Corrupted page {page_id}: {reason}")]
12    CorruptedPage {
13        /// The page number that is corrupted.
14        page_id: u32,
15        /// Human-readable description of the corruption.
16        reason: String,
17    },
18
19    /// A write transaction could not be acquired because another is active.
20    #[error("Transaction conflict: write lock unavailable")]
21    TransactionConflict,
22
23    /// The buffer pool or disk is full.
24    #[error("Out of space: buffer pool or disk full")]
25    OutOfSpace,
26
27    /// The database file does not start with the expected magic bytes.
28    #[error("Invalid magic number")]
29    InvalidMagicNumber,
30
31    /// The database file format version is not supported.
32    #[error("Unsupported version: found {found}, supported {supported}")]
33    UnsupportedVersion {
34        /// The version found in the file.
35        found: u32,
36        /// The version this build supports.
37        supported: u32,
38    },
39
40    /// A checksum did not match the expected value.
41    #[error("Checksum mismatch: expected {expected}, found {found}")]
42    ChecksumMismatch {
43        /// The expected checksum value.
44        expected: u64,
45        /// The actual checksum value found.
46        found: u64,
47    },
48
49    /// Serialization or deserialization failed.
50    #[error("Serialization error: {0}")]
51    SerializationError(String),
52
53    /// The requested node does not exist.
54    #[error("Node not found: {0}")]
55    NodeNotFound(u64),
56
57    /// The requested edge does not exist.
58    #[error("Edge not found: {0}")]
59    EdgeNotFound(u64),
60
61    /// A Cypher query could not be parsed.
62    #[error("Parse error at line {line}, column {column}: {message}")]
63    ParseError {
64        /// The 1-based line number where the error occurred.
65        line: usize,
66        /// The 1-based column number where the error occurred.
67        column: usize,
68        /// Description of the parse error.
69        message: String,
70    },
71
72    /// A semantically invalid query was detected.
73    #[error("Semantic error: {0}")]
74    SemanticError(String),
75
76    /// An error occurred during query execution.
77    #[error("Execution error: {0}")]
78    ExecutionError(String),
79
80    /// The query uses syntax not yet implemented.
81    #[error("Unsupported syntax: {0}")]
82    UnsupportedSyntax(String),
83
84    /// A constraint (e.g. uniqueness) was violated.
85    #[error("Constraint violation: {0}")]
86    ConstraintViolation(String),
87
88    /// A datetime string could not be parsed.
89    #[error("Invalid datetime format: {0}")]
90    InvalidDateTimeFormat(String),
91
92    /// Attempt to write a system-managed property (prefixed with `_`).
93    #[error("System property is read-only: {0}")]
94    SystemPropertyReadOnly(String),
95
96    /// The database file is already locked by another process.
97    #[error("Database is locked: {0}")]
98    DatabaseLocked(String),
99
100    /// The database requires features not compiled into this binary.
101    #[error("Feature incompatible: database requires flags 0x{db_flags:08X}, compiled with 0x{compiled_flags:08X}")]
102    FeatureIncompatible {
103        /// The feature flags stored in the database header.
104        db_flags: u32,
105        /// The feature flags compiled into this binary.
106        compiled_flags: u32,
107    },
108
109    /// The requested subgraph does not exist.
110    #[cfg(feature = "subgraph")]
111    #[error("Subgraph not found: {0}")]
112    SubgraphNotFound(u64),
113
114    /// An operation requires subgraph support but the feature is not compiled.
115    #[cfg(feature = "subgraph")]
116    #[error("Feature requires subgraph support (compile with --features subgraph)")]
117    FeatureRequiresSubgraph,
118
119    /// The requested hyperedge does not exist.
120    #[cfg(feature = "hypergraph")]
121    #[error("Hyperedge not found: {0}")]
122    HyperEdgeNotFound(u64),
123
124    /// A generic plugin error occurred.
125    #[cfg(feature = "plugin")]
126    #[error("Plugin error: {0}")]
127    PluginError(String),
128
129    /// A requested custom function was not found.
130    #[cfg(feature = "plugin")]
131    #[error("Function not found: {0}")]
132    FunctionNotFound(String),
133
134    /// An unsupported index type was requested.
135    #[cfg(feature = "plugin")]
136    #[error("Unsupported index type: {0}")]
137    UnsupportedIndexType(String),
138
139    /// An unsupported serialization format was requested.
140    #[cfg(feature = "plugin")]
141    #[error("Unsupported format: {0}")]
142    UnsupportedFormat(String),
143
144    /// An error occurred during trigger execution.
145    #[cfg(feature = "plugin")]
146    #[error("Trigger error: {0}")]
147    TriggerError(String),
148}
149
150/// Convenience type alias for CypherLite operations.
151pub type Result<T> = std::result::Result<T, CypherLiteError>;
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    // REQ-PAGE-007: InvalidMagicNumber error exists
158    #[test]
159    fn test_invalid_magic_number_error() {
160        let err = CypherLiteError::InvalidMagicNumber;
161        assert_eq!(format!("{err}"), "Invalid magic number");
162    }
163
164    // REQ-PAGE-008: UnsupportedVersion error with found/supported
165    #[test]
166    fn test_unsupported_version_error() {
167        let err = CypherLiteError::UnsupportedVersion {
168            found: 99,
169            supported: 1,
170        };
171        assert_eq!(
172            format!("{err}"),
173            "Unsupported version: found 99, supported 1"
174        );
175    }
176
177    // REQ-WAL-007: Checksum mismatch error
178    #[test]
179    fn test_checksum_mismatch_error() {
180        let err = CypherLiteError::ChecksumMismatch {
181            expected: 100,
182            found: 200,
183        };
184        assert_eq!(
185            format!("{err}"),
186            "Checksum mismatch: expected 100, found 200"
187        );
188    }
189
190    // REQ-TX-010: Transaction conflict error
191    #[test]
192    fn test_transaction_conflict_error() {
193        let err = CypherLiteError::TransactionConflict;
194        assert_eq!(
195            format!("{err}"),
196            "Transaction conflict: write lock unavailable"
197        );
198    }
199
200    // REQ-BUF-006: Out of space error
201    #[test]
202    fn test_out_of_space_error() {
203        let err = CypherLiteError::OutOfSpace;
204        assert_eq!(format!("{err}"), "Out of space: buffer pool or disk full");
205    }
206
207    #[test]
208    fn test_corrupted_page_error() {
209        let err = CypherLiteError::CorruptedPage {
210            page_id: 5,
211            reason: "bad header".to_string(),
212        };
213        assert_eq!(format!("{err}"), "Corrupted page 5: bad header");
214    }
215
216    #[test]
217    fn test_io_error_conversion() {
218        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
219        let err: CypherLiteError = io_err.into();
220        assert!(matches!(err, CypherLiteError::IoError(_)));
221        assert!(format!("{err}").contains("file missing"));
222    }
223
224    #[test]
225    fn test_error_is_send_sync() {
226        fn assert_send<T: Send>() {}
227        fn assert_sync<T: Sync>() {}
228        assert_send::<CypherLiteError>();
229        assert_sync::<CypherLiteError>();
230    }
231
232    #[test]
233    fn test_node_not_found_error() {
234        let err = CypherLiteError::NodeNotFound(42);
235        assert_eq!(format!("{err}"), "Node not found: 42");
236    }
237
238    #[test]
239    fn test_edge_not_found_error() {
240        let err = CypherLiteError::EdgeNotFound(99);
241        assert_eq!(format!("{err}"), "Edge not found: 99");
242    }
243
244    // REQ-QUERY-001: Parse error with location
245    #[test]
246    fn test_parse_error() {
247        let err = CypherLiteError::ParseError {
248            line: 3,
249            column: 10,
250            message: "unexpected token".to_string(),
251        };
252        assert_eq!(
253            format!("{err}"),
254            "Parse error at line 3, column 10: unexpected token"
255        );
256    }
257
258    // REQ-QUERY-002: Semantic error
259    #[test]
260    fn test_semantic_error() {
261        let err = CypherLiteError::SemanticError("unknown label".to_string());
262        assert_eq!(format!("{err}"), "Semantic error: unknown label");
263    }
264
265    // REQ-QUERY-003: Execution error
266    #[test]
267    fn test_execution_error() {
268        let err = CypherLiteError::ExecutionError("division by zero".to_string());
269        assert_eq!(format!("{err}"), "Execution error: division by zero");
270    }
271
272    // REQ-QUERY-004: Unsupported syntax
273    #[test]
274    fn test_unsupported_syntax_error() {
275        let err = CypherLiteError::UnsupportedSyntax("MERGE".to_string());
276        assert_eq!(format!("{err}"), "Unsupported syntax: MERGE");
277    }
278
279    // REQ-QUERY-005: Constraint violation
280    #[test]
281    fn test_constraint_violation_error() {
282        let err = CypherLiteError::ConstraintViolation("unique key violated".to_string());
283        assert_eq!(
284            format!("{err}"),
285            "Constraint violation: unique key violated"
286        );
287    }
288
289    // V-003: SystemPropertyReadOnly error
290    #[test]
291    fn test_system_property_read_only_error() {
292        let err = CypherLiteError::SystemPropertyReadOnly("_created_at".to_string());
293        assert_eq!(
294            format!("{err}"),
295            "System property is read-only: _created_at"
296        );
297    }
298
299    // U-002: InvalidDateTimeFormat error
300    #[test]
301    fn test_invalid_datetime_format_error() {
302        let err = CypherLiteError::InvalidDateTimeFormat("bad input".to_string());
303        assert_eq!(format!("{err}"), "Invalid datetime format: bad input");
304    }
305
306    // AA-T4: FeatureIncompatible error
307    #[test]
308    fn test_feature_incompatible_error() {
309        let err = CypherLiteError::FeatureIncompatible {
310            db_flags: 0x03,
311            compiled_flags: 0x01,
312        };
313        assert_eq!(
314            format!("{err}"),
315            "Feature incompatible: database requires flags 0x00000003, compiled with 0x00000001"
316        );
317    }
318
319    // R-PERSIST-039: DatabaseLocked error with descriptive message
320    #[test]
321    fn test_database_locked_error() {
322        let err = CypherLiteError::DatabaseLocked("/tmp/test.cyl".to_string());
323        assert_eq!(format!("{err}"), "Database is locked: /tmp/test.cyl");
324    }
325
326    #[test]
327    fn test_database_locked_error_is_not_io_error() {
328        let err = CypherLiteError::DatabaseLocked("/tmp/test.cyl".to_string());
329        assert!(matches!(err, CypherLiteError::DatabaseLocked(_)));
330    }
331
332    // ======================================================================
333    // GG-001: SubgraphNotFound error
334    // ======================================================================
335
336    #[cfg(feature = "subgraph")]
337    #[test]
338    fn test_subgraph_not_found_error() {
339        let err = CypherLiteError::SubgraphNotFound(42);
340        assert_eq!(format!("{err}"), "Subgraph not found: 42");
341    }
342
343    #[cfg(feature = "subgraph")]
344    #[test]
345    fn test_feature_requires_subgraph_error() {
346        let err = CypherLiteError::FeatureRequiresSubgraph;
347        assert_eq!(
348            format!("{err}"),
349            "Feature requires subgraph support (compile with --features subgraph)"
350        );
351    }
352}