wires

summary refs log tree commit diff
path: root/wyrd_sqlite/src/error.rs
blob: 281862f3dfead6c464342f257b7b7e30ee37240e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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>>;