wires

summary refs log tree commit diff
path: root/wyrd_sqlite/src/error.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--wyrd_sqlite/src/error.rs84
1 files changed, 84 insertions, 0 deletions
diff --git a/wyrd_sqlite/src/error.rs b/wyrd_sqlite/src/error.rs
new file mode 100644
index 0000000..281862f
--- /dev/null
+++ b/wyrd_sqlite/src/error.rs
@@ -0,0 +1,84 @@
+use std::{
+    ffi::{CStr, NulError, c_int},
+    fmt::{self, Display, Formatter},
+    str::Utf8Error,
+};
+
+use crate::{FromSql, ffi};
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error {
+    #[error(transparent)]
+    Sqlite(#[from] SqliteError),
+    #[error("input contained no SQL")]
+    EmptyStatement,
+    #[error(transparent)]
+    Nul(#[from] NulError),
+    #[error(transparent)]
+    Utf8(#[from] Utf8Error),
+    #[error("invalid column index {0}")]
+    InvalidColumn(c_int),
+    #[error("execute returned results")]
+    ExecReturnedRows,
+    #[error("multiple statements provided")]
+    MultipleStatements,
+}
+
+#[derive(Debug)]
+pub struct SqliteError {
+    code: c_int,
+    msg: Option<String>,
+}
+
+impl Error {
+    pub(crate) fn from_code(code: c_int) -> Self {
+        SqliteError { code, msg: None }.into()
+    }
+
+    pub(crate) fn from_db(db: *mut ffi::sqlite3) -> Self {
+        // SAFETY: sqlite has checks to handle if db is null or dangling, so these shouldn't cause
+        // ub for any input
+        let (code, c_msg) = unsafe { (ffi::sqlite3_errcode(db), ffi::sqlite3_errmsg(db)) };
+
+        let msg = if c_msg.is_null() {
+            None
+        } else {
+            Some(
+                // SAFETY: as long as c_msg is non-null, sqlite shouldn't be giving us bad strings
+                unsafe { CStr::from_ptr(c_msg) }
+                    .to_string_lossy()
+                    .to_string(),
+            )
+        };
+
+        SqliteError { code, msg }.into()
+    }
+}
+
+fn errstr(code: c_int) -> &'static str {
+    // SAFETY: `sqlite3_errstr` always returns a valid null-terminated static string
+    unsafe { CStr::from_ptr(ffi::sqlite3_errstr(code)) }
+        .to_str()
+        .expect("sqlite errors should be valid utf8")
+}
+
+impl Display for SqliteError {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        let msg = self.msg.as_deref().unwrap_or(errstr(self.code));
+        write!(f, "{msg} ({})", self.code)
+    }
+}
+
+impl std::error::Error for SqliteError {}
+
+pub type Result<T> = std::result::Result<T, Error>;
+
+#[derive(Debug, thiserror::Error)]
+pub enum GetError<T: FromSql> {
+    #[error(transparent)]
+    Sqlite(#[from] Error),
+    #[error(transparent)]
+    FromSql(T::Error),
+}
+
+pub type GetResult<T> = std::result::Result<T, GetError<T>>;