chore: clear error conversion

This commit is contained in:
kaichaosun 2026-03-30 16:44:53 +08:00
parent 45ad505508
commit 41a1599f9f
No known key found for this signature in database
GPG Key ID: 223E0F992F4F03BF
14 changed files with 122 additions and 62 deletions

41
Cargo.lock generated
View File

@ -62,9 +62,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cc"
version = "1.2.54"
version = "1.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1"
dependencies = [
"find-msvc-tools",
"shlex",
@ -108,6 +108,19 @@ dependencies = [
"prost",
]
[[package]]
name = "chat-sqlite"
version = "0.1.0"
dependencies = [
"crypto",
"hex",
"rusqlite",
"storage",
"tempfile",
"thiserror",
"zeroize",
]
[[package]]
name = "cipher"
version = "0.4.4"
@ -233,12 +246,12 @@ version = "0.0.1"
dependencies = [
"blake2",
"chacha20poly1305",
"chat-sqlite",
"hkdf",
"rand",
"rand_core",
"safer-ffi",
"serde",
"sqlite",
"storage",
"tempfile",
"thiserror",
@ -353,9 +366,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
[[package]]
name = "find-msvc-tools"
version = "0.1.8"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "foldhash"
@ -504,13 +517,13 @@ dependencies = [
"base64",
"blake2",
"chat-proto",
"chat-sqlite",
"crypto",
"double-ratchets",
"hex",
"prost",
"rand_core",
"safer-ffi",
"sqlite",
"storage",
"tempfile",
"thiserror",
@ -580,9 +593,9 @@ dependencies = [
[[package]]
name = "openssl-sys"
version = "0.9.111"
version = "0.9.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
dependencies = [
"cc",
"libc",
@ -893,17 +906,6 @@ dependencies = [
"der",
]
[[package]]
name = "sqlite"
version = "0.1.0"
dependencies = [
"crypto",
"hex",
"storage",
"tempfile",
"zeroize",
]
[[package]]
name = "stabby"
version = "36.2.2"
@ -944,7 +946,6 @@ name = "storage"
version = "0.1.0"
dependencies = [
"crypto",
"rusqlite",
"thiserror",
]

View File

@ -8,7 +8,7 @@ crate-type = ["rlib","staticlib","dylib"]
[dependencies]
base64 = "0.22"
sqlite = { path = "../sqlite" }
chat-sqlite = { path = "../sqlite" }
blake2.workspace = true
chat-proto = { git = "https://github.com/logos-messaging/chat_proto" }
crypto = { path = "../crypto" }

View File

@ -27,5 +27,5 @@ serde = "1.0"
headers = ["safer-ffi/headers"]
[dev-dependencies]
sqlite = { path = "../sqlite" }
tempfile = "3"
chat-sqlite = { path = "../sqlite" }
tempfile = "3"

View File

@ -10,7 +10,6 @@ pub mod types;
pub use keypair::InstallationKeyPair;
pub use state::{Header, RatchetState, SkippedKey};
pub use storage::StorageConfig;
pub use storage::{
RatchetSession, SessionError, restore_ratchet_state, to_ratchet_record, to_skipped_key_records,
};

View File

@ -9,5 +9,5 @@ mod types;
pub use errors::SessionError;
pub use session::RatchetSession;
pub use storage::{RatchetStateRecord, RatchetStore, SkippedKeyRecord, StorageConfig, StorageError};
pub use storage::{RatchetStateRecord, RatchetStore, SkippedKeyRecord, StorageError};
pub use types::{restore_ratchet_state, to_ratchet_record, to_skipped_key_records};

View File

@ -1,5 +1,5 @@
[package]
name = "sqlite"
name = "chat-sqlite"
version = "0.1.0"
edition = "2024"
description = "SQLite storage implementation for libchat"
@ -9,6 +9,8 @@ crypto = { path = "../crypto" }
hex = "0.4.3"
storage = { path = "../storage" }
zeroize = { version = "1.8.2", features = ["derive"] }
rusqlite = { version = "0.35", features = ["bundled-sqlcipher-vendored-openssl"] }
thiserror = "2"
[dev-dependencies]
tempfile = "3"

View File

@ -1,9 +1,10 @@
//! SQLite storage backend.
use rusqlite::Connection;
use rusqlite::{Connection, Row, Transaction};
use std::path::Path;
use storage::StorageError;
use crate::StorageError;
use crate::errors::SqliteError;
/// Configuration for SQLite storage.
#[derive(Debug, Clone)]
@ -16,17 +17,51 @@ pub enum StorageConfig {
Encrypted { path: String, key: String },
}
pub struct DbConn(rusqlite::Connection);
impl DbConn {
fn map_err(e: rusqlite::Error) -> StorageError {
StorageError::Database(e.to_string())
}
pub fn prepare(&self, sql: &str) -> Result<rusqlite::Statement<'_>, StorageError> {
self.0.prepare(sql).map_err(Self::map_err)
}
pub fn transaction(&mut self) -> Result<Transaction<'_>, StorageError> {
self.0.transaction().map_err(Self::map_err)
}
pub fn execute(&self, sql: &str, params: impl rusqlite::Params) -> Result<usize, StorageError> {
self.0.execute(sql, params).map_err(Self::map_err)
}
pub fn execute_batch(&self, sql: &str) -> Result<(), StorageError> {
self.0.execute_batch(sql).map_err(Self::map_err)
}
pub fn query_row<T, F>(
&self,
sql: &str,
params: impl rusqlite::Params,
f: F,
) -> Result<T, StorageError>
where
F: FnOnce(&Row) -> Result<T, rusqlite::Error>,
{
self.0.query_row(sql, params, f).map_err(Self::map_err)
}
}
/// SQLite database wrapper.
///
/// This provides the core database connection and can be shared
/// across different domain-specific storage implementations.
pub struct SqliteDb {
conn: Connection,
conn: DbConn,
}
impl SqliteDb {
/// Creates a new SQLite database with the given configuration.
pub fn new(config: StorageConfig) -> Result<Self, StorageError> {
pub fn new(config: StorageConfig) -> Result<Self, SqliteError> {
let conn = match config {
StorageConfig::InMemory => Connection::open_in_memory()?,
StorageConfig::File(ref path) => Connection::open(path)?,
@ -40,41 +75,41 @@ impl SqliteDb {
// Enable foreign keys
conn.execute_batch("PRAGMA foreign_keys = ON;")?;
Ok(Self { conn })
Ok(Self { conn: DbConn(conn) })
}
/// Opens an existing database file.
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, StorageError> {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, SqliteError> {
let conn = Connection::open(path)?;
conn.execute_batch("PRAGMA foreign_keys = ON;")?;
Ok(Self { conn })
Ok(Self { conn: DbConn(conn) })
}
/// Creates an in-memory database (useful for testing).
pub fn in_memory() -> Result<Self, StorageError> {
pub fn in_memory() -> Result<Self, SqliteError> {
Self::new(StorageConfig::InMemory)
}
pub fn sqlcipher(path: String, key: String) -> Result<Self, StorageError> {
pub fn sqlcipher(path: String, key: String) -> Result<Self, SqliteError> {
Self::new(StorageConfig::Encrypted { path, key })
}
/// Returns a reference to the underlying connection.
///
/// Use this for domain-specific storage operations.
pub fn connection(&self) -> &Connection {
pub fn connection(&self) -> &DbConn {
&self.conn
}
/// Returns a mutable reference to the underlying connection.
///
/// Use this for operations that require mutable access, such as transactions.
pub fn connection_mut(&mut self) -> &mut Connection {
pub fn connection_mut(&mut self) -> &mut DbConn {
&mut self.conn
}
/// Begins a transaction.
pub fn transaction(&mut self) -> Result<rusqlite::Transaction<'_>, StorageError> {
pub fn transaction(&mut self) -> Result<rusqlite::Transaction<'_>, SqliteError> {
Ok(self.conn.transaction()?)
}
}

32
core/sqlite/src/errors.rs Normal file
View File

@ -0,0 +1,32 @@
use storage::StorageError;
use thiserror::Error;
// #[derive(Debug, thiserror::Error, Display)]
// pub struct SqliteError(pub rusqlite::Error);
//
// #[derive(Debug, thiserror::Error)]
// pub enum SqliteError {
// #[error(transparent)]
// Rusqlite(#[from] rusqlite::Error),
// #[error(transparent)]
// Storage(#[from] StorageError),
// }
#[derive(Debug, Error)]
pub enum SqliteError {
#[error("sqlite error: {0}")]
Rusqlite(#[from] rusqlite::Error),
#[error(transparent)]
Storage(#[from] StorageError),
}
// impl From<SqliteError> for StorageError {
// fn from(err: SqliteError) -> Self {
// match err {
// SqliteError::Storage(e) => e,
// SqliteError::Rusqlite(e) => StorageError::Database(e.to_string()),
// }
// }
// }

View File

@ -1,19 +1,24 @@
//! Chat-specific SQLite storage implementation.
mod common;
mod errors;
mod migrations;
mod types;
use std::collections::HashSet;
use crypto::{Identity, PrivateKey};
use rusqlite::{Error as RusqliteError, Transaction, params};
use storage::{
ConversationKind, ConversationMeta, ConversationStore, EphemeralKeyStore, IdentityStore,
RatchetStateRecord, RatchetStore, RusqliteError, SkippedKeyRecord, SqliteDb, StorageConfig,
StorageError, Transaction, params,
RatchetStateRecord, RatchetStore, SkippedKeyRecord, StorageError,
};
use zeroize::Zeroize;
use crate::types::IdentityRecord;
use crate::{
common::{SqliteDb, StorageConfig},
types::IdentityRecord,
};
/// Chat-specific storage operations.
///
@ -440,7 +445,6 @@ fn blob_to_array<const N: usize>(blob: Vec<u8>) -> [u8; N] {
mod tests {
use storage::{
ConversationKind, ConversationMeta, ConversationStore, EphemeralKeyStore, IdentityStore,
StorageConfig,
};
use super::*;

View File

@ -3,7 +3,7 @@
//! SQL migrations are embedded at compile time and applied in order.
//! Each migration is applied atomically within a transaction.
use storage::{Connection, StorageError};
use crate::{common::DbConn, errors::SqliteError};
/// Embeds and returns all migration SQL files in order.
pub fn get_migrations() -> Vec<(&'static str, &'static str)> {
@ -22,7 +22,7 @@ pub fn get_migrations() -> Vec<(&'static str, &'static str)> {
/// Applies all migrations to the database.
///
/// Uses a simple version tracking table to avoid re-running migrations.
pub fn apply_migrations(conn: &mut Connection) -> Result<(), StorageError> {
pub fn apply_migrations(conn: &mut DbConn) -> Result<(), SqliteError> {
// Create migrations tracking table if it doesn't exist
conn.execute_batch(
"CREATE TABLE IF NOT EXISTS _migrations (

View File

@ -7,4 +7,3 @@ description = "Shared storage layer for libchat"
[dependencies]
crypto = { path = "../crypto" }
thiserror = "2"
rusqlite = { version = "0.35", features = ["bundled-sqlcipher-vendored-openssl"] }

View File

@ -31,9 +31,3 @@ pub enum StorageError {
#[error("invalid data: {0}")]
InvalidData(String),
}
impl From<rusqlite::Error> for StorageError {
fn from(e: rusqlite::Error) -> Self {
StorageError::Database(e.to_string())
}
}

View File

@ -3,18 +3,13 @@
//! This crate provides a common storage abstraction that can be used by
//! multiple crates in the libchat workspace (double-ratchets, conversations, etc.).
//!
//! Uses SQLCipher for encrypted SQLite storage.
//! The storage implementation is handled by other crates.
mod errors;
mod sqlite;
mod store;
pub use errors::StorageError;
pub use sqlite::{SqliteDb, StorageConfig};
pub use store::{
ChatStore, ConversationKind, ConversationMeta, ConversationStore, EphemeralKeyStore,
IdentityStore, RatchetStateRecord, RatchetStore, SkippedKeyRecord,
};
// Re-export rusqlite types that domain crates will need
pub use rusqlite::{Connection, Error as RusqliteError, Transaction, params};

View File

@ -101,10 +101,8 @@ pub trait RatchetStore {
) -> Result<(), StorageError>;
/// Loads ratchet state for a conversation.
fn load_ratchet_state(
&self,
conversation_id: &str,
) -> Result<RatchetStateRecord, StorageError>;
fn load_ratchet_state(&self, conversation_id: &str)
-> Result<RatchetStateRecord, StorageError>;
/// Loads skipped keys for a conversation.
fn load_skipped_keys(
@ -124,4 +122,5 @@ pub trait RatchetStore {
pub trait ChatStore: IdentityStore + EphemeralKeyStore + ConversationStore + RatchetStore {}
impl<T> ChatStore for T where T: IdentityStore + EphemeralKeyStore + ConversationStore + RatchetStore {}
impl<T> ChatStore for T where T: IdentityStore + EphemeralKeyStore + ConversationStore + RatchetStore
{}