Skip to main content

mixtape_tools/sqlite/
error.rs

1//! SQLite-specific error types
2
3use mixtape_core::ToolError;
4use std::path::PathBuf;
5use thiserror::Error;
6
7/// Errors that can occur during SQLite tool operations
8#[derive(Debug, Error)]
9pub enum SqliteToolError {
10    /// Database not found or not opened
11    #[error("Database not found: {0}")]
12    DatabaseNotFound(String),
13
14    /// No default database is set
15    #[error("No default database set. Open a database first or specify one explicitly.")]
16    NoDefaultDatabase,
17
18    /// Failed to open/create database connection
19    #[error("Failed to connect to database '{path}': {message}")]
20    ConnectionFailed { path: PathBuf, message: String },
21
22    /// Database file already exists when create=false
23    #[error("Database does not exist: {0}")]
24    DatabaseDoesNotExist(PathBuf),
25
26    /// SQLite query execution error
27    #[error("Query error: {0}")]
28    QueryError(String),
29
30    /// Invalid query for the operation type
31    #[error("Invalid query: {0}")]
32    InvalidQuery(String),
33
34    /// Transaction-related errors
35    #[error("Transaction error: {0}")]
36    TransactionError(String),
37
38    /// Path validation or filesystem error
39    #[error("Path error: {0}")]
40    PathError(String),
41
42    /// Serialization/deserialization error
43    #[error("Serialization error: {0}")]
44    SerializationError(String),
45
46    /// Table not found
47    #[error("Table not found: {0}")]
48    TableNotFound(String),
49
50    /// Migration not found
51    #[error("Migration not found: {0}")]
52    MigrationNotFound(String),
53
54    /// Migration checksum mismatch (integrity violation)
55    #[error("Migration checksum mismatch for '{version}': expected {expected}, got {actual}")]
56    MigrationChecksumMismatch {
57        version: String,
58        expected: String,
59        actual: String,
60    },
61
62    /// Generic SQLite error wrapper
63    #[error("SQLite error: {0}")]
64    Sqlite(#[from] rusqlite::Error),
65
66    /// IO error
67    #[error("IO error: {0}")]
68    Io(#[from] std::io::Error),
69}
70
71impl From<SqliteToolError> for ToolError {
72    fn from(err: SqliteToolError) -> Self {
73        ToolError::Custom(err.to_string())
74    }
75}
76
77impl From<serde_json::Error> for SqliteToolError {
78    fn from(err: serde_json::Error) -> Self {
79        SqliteToolError::SerializationError(err.to_string())
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn test_database_not_found_display() {
89        let err = SqliteToolError::DatabaseNotFound("test.db".to_string());
90        assert_eq!(err.to_string(), "Database not found: test.db");
91    }
92
93    #[test]
94    fn test_no_default_database_display() {
95        let err = SqliteToolError::NoDefaultDatabase;
96        assert!(err.to_string().contains("No default database set"));
97    }
98
99    #[test]
100    fn test_connection_failed_display() {
101        let err = SqliteToolError::ConnectionFailed {
102            path: PathBuf::from("/tmp/test.db"),
103            message: "permission denied".to_string(),
104        };
105        let msg = err.to_string();
106        assert!(msg.contains("/tmp/test.db"));
107        assert!(msg.contains("permission denied"));
108    }
109
110    #[test]
111    fn test_database_does_not_exist_display() {
112        let err = SqliteToolError::DatabaseDoesNotExist(PathBuf::from("/missing.db"));
113        assert!(err.to_string().contains("/missing.db"));
114    }
115
116    #[test]
117    fn test_query_error_display() {
118        let err = SqliteToolError::QueryError("syntax error".to_string());
119        assert_eq!(err.to_string(), "Query error: syntax error");
120    }
121
122    #[test]
123    fn test_invalid_query_display() {
124        let err = SqliteToolError::InvalidQuery("SELECT not allowed".to_string());
125        assert_eq!(err.to_string(), "Invalid query: SELECT not allowed");
126    }
127
128    #[test]
129    fn test_transaction_error_display() {
130        let err = SqliteToolError::TransactionError("no active transaction".to_string());
131        assert_eq!(err.to_string(), "Transaction error: no active transaction");
132    }
133
134    #[test]
135    fn test_path_error_display() {
136        let err = SqliteToolError::PathError("invalid path".to_string());
137        assert_eq!(err.to_string(), "Path error: invalid path");
138    }
139
140    #[test]
141    fn test_serialization_error_display() {
142        let err = SqliteToolError::SerializationError("invalid JSON".to_string());
143        assert_eq!(err.to_string(), "Serialization error: invalid JSON");
144    }
145
146    #[test]
147    fn test_table_not_found_display() {
148        let err = SqliteToolError::TableNotFound("users".to_string());
149        assert_eq!(err.to_string(), "Table not found: users");
150    }
151
152    #[test]
153    fn test_from_sqlite_error() {
154        // Create a rusqlite error by trying to prepare an invalid statement
155        let conn = rusqlite::Connection::open_in_memory().unwrap();
156        let sqlite_err = conn.prepare("INVALID SQL SYNTAX").unwrap_err();
157        let err: SqliteToolError = sqlite_err.into();
158        assert!(err.to_string().contains("SQLite error"));
159    }
160
161    #[test]
162    fn test_from_io_error() {
163        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
164        let err: SqliteToolError = io_err.into();
165        assert!(err.to_string().contains("IO error"));
166        assert!(err.to_string().contains("file not found"));
167    }
168
169    #[test]
170    fn test_from_serde_json_error() {
171        let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
172        let err: SqliteToolError = json_err.into();
173        assert!(err.to_string().contains("Serialization error"));
174    }
175
176    #[test]
177    fn test_into_tool_error() {
178        let err = SqliteToolError::DatabaseNotFound("test.db".to_string());
179        let tool_err: ToolError = err.into();
180        match tool_err {
181            ToolError::Custom(msg) => assert!(msg.contains("test.db")),
182            _ => panic!("Expected ToolError::Custom"),
183        }
184    }
185}