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, } 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 = std::result::Result; #[derive(Debug, thiserror::Error)] pub enum GetError { #[error(transparent)] Sqlite(#[from] Error), #[error(transparent)] FromSql(T::Error), } pub type GetResult = std::result::Result>;