Skip to main content

sqlite_provider/
error.rs

1use core::fmt;
2
3/// Error returned by sqlite-provider operations.
4#[derive(Clone, Debug)]
5pub struct Error {
6    /// Primary SQLite-style result code for the failure.
7    pub code: ErrorCode,
8    /// Optional extended SQLite result code when provided by backend.
9    pub extended: Option<i32>,
10    /// Optional human-readable error message from backend or wrapper.
11    pub message: Option<String>,
12}
13
14/// Result alias used across the crate.
15pub type Result<T> = core::result::Result<T, Error>;
16
17/// SQLite result codes plus crate-specific conditions.
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum ErrorCode {
20    /// Operation completed successfully.
21    Ok,
22    /// Generic SQL error or missing database.
23    Error,
24    /// Internal SQLite logic error.
25    Internal,
26    /// Access permission denied.
27    Perm,
28    /// Operation aborted.
29    Abort,
30    /// Database file is busy.
31    Busy,
32    /// Database object is locked.
33    Locked,
34    /// Memory allocation failed.
35    NoMem,
36    /// Attempt to write a read-only database.
37    ReadOnly,
38    /// Operation interrupted.
39    Interrupt,
40    /// Disk I/O error.
41    IoErr,
42    /// Database image is malformed.
43    Corrupt,
44    /// Requested operation not found.
45    NotFound,
46    /// Database or disk is full.
47    Full,
48    /// Unable to open database file.
49    CantOpen,
50    /// Locking protocol error.
51    Protocol,
52    /// Database is empty.
53    Empty,
54    /// Database schema changed.
55    Schema,
56    /// String or blob too large.
57    TooBig,
58    /// Constraint violation.
59    Constraint,
60    /// Datatype mismatch.
61    Mismatch,
62    /// API misuse.
63    Misuse,
64    /// Large-file support unavailable.
65    NoLfs,
66    /// Authorization denied.
67    Auth,
68    /// Auxiliary database format error.
69    Format,
70    /// Parameter index out of range.
71    Range,
72    /// File opened is not a database.
73    NotADb,
74    /// Informational notice.
75    Notice,
76    /// Warning condition.
77    Warning,
78    /// `sqlite3_step` produced a row.
79    Row,
80    /// `sqlite3_step` completed without row.
81    Done,
82    /// Requested optional capability is unavailable.
83    FeatureUnavailable,
84    /// Unknown or backend-specific result code.
85    Unknown(i32),
86}
87
88impl ErrorCode {
89    /// Decode a raw SQLite result code (including extended codes).
90    pub const fn from_code(code: i32) -> ErrorCode {
91        let primary = code & 0xff;
92        match primary {
93            0 => ErrorCode::Ok,
94            1 => ErrorCode::Error,
95            2 => ErrorCode::Internal,
96            3 => ErrorCode::Perm,
97            4 => ErrorCode::Abort,
98            5 => ErrorCode::Busy,
99            6 => ErrorCode::Locked,
100            7 => ErrorCode::NoMem,
101            8 => ErrorCode::ReadOnly,
102            9 => ErrorCode::Interrupt,
103            10 => ErrorCode::IoErr,
104            11 => ErrorCode::Corrupt,
105            12 => ErrorCode::NotFound,
106            13 => ErrorCode::Full,
107            14 => ErrorCode::CantOpen,
108            15 => ErrorCode::Protocol,
109            16 => ErrorCode::Empty,
110            17 => ErrorCode::Schema,
111            18 => ErrorCode::TooBig,
112            19 => ErrorCode::Constraint,
113            20 => ErrorCode::Mismatch,
114            21 => ErrorCode::Misuse,
115            22 => ErrorCode::NoLfs,
116            23 => ErrorCode::Auth,
117            24 => ErrorCode::Format,
118            25 => ErrorCode::Range,
119            26 => ErrorCode::NotADb,
120            27 => ErrorCode::Notice,
121            28 => ErrorCode::Warning,
122            100 => ErrorCode::Row,
123            101 => ErrorCode::Done,
124            _ => ErrorCode::Unknown(code),
125        }
126    }
127
128    /// Encode this error code to the SQLite numeric code when available.
129    pub const fn code(self) -> Option<i32> {
130        match self {
131            ErrorCode::Ok => Some(0),
132            ErrorCode::Error => Some(1),
133            ErrorCode::Internal => Some(2),
134            ErrorCode::Perm => Some(3),
135            ErrorCode::Abort => Some(4),
136            ErrorCode::Busy => Some(5),
137            ErrorCode::Locked => Some(6),
138            ErrorCode::NoMem => Some(7),
139            ErrorCode::ReadOnly => Some(8),
140            ErrorCode::Interrupt => Some(9),
141            ErrorCode::IoErr => Some(10),
142            ErrorCode::Corrupt => Some(11),
143            ErrorCode::NotFound => Some(12),
144            ErrorCode::Full => Some(13),
145            ErrorCode::CantOpen => Some(14),
146            ErrorCode::Protocol => Some(15),
147            ErrorCode::Empty => Some(16),
148            ErrorCode::Schema => Some(17),
149            ErrorCode::TooBig => Some(18),
150            ErrorCode::Constraint => Some(19),
151            ErrorCode::Mismatch => Some(20),
152            ErrorCode::Misuse => Some(21),
153            ErrorCode::NoLfs => Some(22),
154            ErrorCode::Auth => Some(23),
155            ErrorCode::Format => Some(24),
156            ErrorCode::Range => Some(25),
157            ErrorCode::NotADb => Some(26),
158            ErrorCode::Notice => Some(27),
159            ErrorCode::Warning => Some(28),
160            ErrorCode::Row => Some(100),
161            ErrorCode::Done => Some(101),
162            ErrorCode::FeatureUnavailable => None,
163            ErrorCode::Unknown(code) => Some(code),
164        }
165    }
166}
167
168impl Error {
169    /// Create an error with only a primary code.
170    pub fn new(code: ErrorCode) -> Self {
171        Self {
172            code,
173            extended: None,
174            message: None,
175        }
176    }
177
178    /// Create an error with a primary code and owned message text.
179    pub fn with_message(code: ErrorCode, message: impl Into<String>) -> Self {
180        Self {
181            code,
182            extended: None,
183            message: Some(message.into()),
184        }
185    }
186
187    /// Create an error from raw SQLite result code fields.
188    pub fn from_code(code: i32, message: Option<String>, extended: Option<i32>) -> Self {
189        Self {
190            code: ErrorCode::from_code(code),
191            extended,
192            message,
193        }
194    }
195
196    /// Create a capability error when a requested optional feature is unavailable.
197    pub fn feature_unavailable(msg: &'static str) -> Self {
198        Self {
199            code: ErrorCode::FeatureUnavailable,
200            extended: None,
201            message: Some(msg.into()),
202        }
203    }
204}
205
206impl fmt::Display for Error {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        match (self.code, &self.message) {
209            (_, Some(msg)) => write!(f, "{}", msg),
210            (ErrorCode::Unknown(code), None) => write!(f, "sqlite error code {}", code),
211            (ErrorCode::FeatureUnavailable, None) => write!(f, "feature unavailable"),
212            (code, None) => write!(f, "sqlite error {:?}", code),
213        }
214    }
215}
216
217impl std::error::Error for Error {}
218
219#[cfg(test)]
220mod tests {
221    use super::{Error, ErrorCode};
222
223    #[test]
224    fn error_code_mapping() {
225        assert_eq!(ErrorCode::from_code(0), ErrorCode::Ok);
226        assert_eq!(ErrorCode::from_code(19), ErrorCode::Constraint);
227        // Extended result code should map by primary low byte.
228        assert_eq!(ErrorCode::from_code((8 << 8) | 19), ErrorCode::Constraint);
229        assert_eq!(ErrorCode::from_code(14), ErrorCode::CantOpen);
230        assert_eq!(ErrorCode::from_code(999), ErrorCode::Unknown(999));
231    }
232
233    #[test]
234    fn feature_unavailable_message() {
235        let err = Error::feature_unavailable("missing");
236        assert_eq!(err.code, ErrorCode::FeatureUnavailable);
237        assert_eq!(err.message.as_deref(), Some("missing"));
238    }
239}