use super::DataType;
use libsqlite3_sys::{self as ffi};
use std::{
ffi::{CStr, NulError},
str::Utf8Error,
};
macro_rules! display_error {
(@@
$self:ident, $me:ident, $f:ident,
$(, #prefix $s:literal)?
$(, #delegate $($id:ident)*)?
$(, $($id2:pat => ($fmt:literal$($tt:tt)*)),* )? $(,)?
) => {
$(write!($f, $s)?;)?
return match $me {
$($(Self::$id(e) => std::fmt::Display::fmt(e,$f),)*)?
$(
$($id2 => write!($f, $fmt $($tt)*)),*
)?
}
};
(@@ $self:ident, $me:ident, $f:ident, , $pat:pat => write!($($tt:tt)*)) => {{
let $pat = $me;
return write!($f, $($tt)*);
}};
($self:ident $($tt:tt)*) => {
impl std::error::Error for $self { }
impl std::fmt::Debug for $self {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl std::fmt::Display for $self {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
display_error!(@@ $self, self, f, $($tt)*);
}
}
};
}
macro_rules! is_busy {
($me:ty, $pr:pat => $($tt:tt)*) => {
impl $me {
pub fn is_busy(&self) -> bool {
let $pr = self;
$($tt)*
}
}
};
}
macro_rules! from {
(
$me:ty,
$(for $to:ty => $id:ident),* $(,)?
$(<$t2:ty> $id2:pat => $b2:expr),* $(,)?
) => {
$(
impl From<$to> for $me {
fn from(value: $to) -> Self {
Self::$id(value.into())
}
}
)*
$(
impl From<$t2> for $me {
fn from($id2: $t2) -> Self {
$b2
}
}
)*
};
}
macro_rules! opaque_error {
(
$me:ident,
#failedto $display:literal $(,)?
) => {
#[doc = concat!("An error when failed to ",$display)]
pub struct $me(DatabaseError);
from!($me, <DatabaseError> err => Self(err));
is_busy!($me, me => me.0.is_busy());
impl std::error::Error for $me { }
impl std::fmt::Debug for $me {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(self, f)
}
}
impl std::fmt::Display for $me {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Failed to {}: {}", $display, self.0)
}
}
};
}
pub(crate) use display_error;
pub(crate) use is_busy;
pub(crate) use from;
pub(crate) use opaque_error;
pub struct DatabaseError {
pub message: String,
pub code: i32,
}
impl DatabaseError {
pub(crate) fn from_code(result: i32, db: *mut ffi::sqlite3) -> Self {
let data = unsafe { ffi::sqlite3_errmsg(db) };
if data.is_null() {
return Self { message: ffi::code_to_str(result).into(), code: result }
}
let msg = match unsafe { CStr::from_ptr(data) }.to_str() {
Ok(ok) => ok.into(),
Err(_) => ffi::code_to_str(result).into(),
};
Self { message: msg, code: result }
}
pub fn is_busy(&self) -> bool {
self.code == ffi::SQLITE_BUSY
}
}
impl std::error::Error for DatabaseError {}
impl std::fmt::Debug for DatabaseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::fmt::Display for DatabaseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
pub enum StringError {
TooLarge,
Utf8(Utf8Error),
NulError(NulError),
}
from! {
StringError,
for NulError => NulError,
for Utf8Error => Utf8
}
display_error! {
StringError,
#prefix "Failed to convert rust to sqlite string: ",
#delegate NulError Utf8,
Self::TooLarge => ("string too large, sqlite max string is i32::MAX"),
}
pub enum OpenError {
NotSerializeMode,
String(StringError),
Database(DatabaseError),
}
impl From<std::ffi::NulError> for OpenError {
fn from(value: std::ffi::NulError) -> Self {
Self::String(value.into())
}
}
from! {
OpenError,
for DatabaseError => Database,
for StringError => String,
for Utf8Error => String
}
is_busy!(OpenError, me => matches!(me,OpenError::Database(d) if d.is_busy()));
display_error! {
OpenError,
#prefix "Failed to open a database: ",
#delegate Database String,
Self::NotSerializeMode => ("sqlite is not in serialize mode"),
}
opaque_error!(ConfigureError, #failedto "configure database");
opaque_error!(PrepareError, #failedto "create prepared statement");
opaque_error!(StepError, #failedto "get the next row");
opaque_error!(ResetError, #failedto "reset or clear binding prepared statement");
pub enum BindError {
String(StringError),
Database(DatabaseError),
}
from! {
BindError,
for StringError => String,
for DatabaseError => Database
}
display_error! {
BindError,
#prefix "Failed to bind value: ",
#delegate String Database
}
pub enum DecodeError {
IndexOutOfBounds,
InvalidDataType { expect: DataType, found: DataType },
Utf8(Utf8Error),
}
display_error! {
DecodeError,
#prefix "Failed to decode value: ",
#delegate Utf8,
Self::IndexOutOfBounds => ("row index out of bounds"),
Self::InvalidDataType { expect, found }=> ("datatype requested missmatch, expect `{expect}` found `{found}`"),
}