#![cfg_attr(
not(test),
deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing
)
)]
mod file;
mod memory;
#[cfg(feature = "mongodb")]
mod mongo;
#[cfg(feature = "mssql")]
mod mssql;
mod noop;
#[cfg(feature = "redb")]
mod redb;
#[cfg(feature = "sql")]
mod sql;
use std::path::PathBuf;
use std::sync::Arc;
use async_trait::async_trait;
use thiserror::Error;
pub use file::{CachedFileStore, FileStore, FileStoreOptions};
pub use memory::MemoryStore;
#[cfg(feature = "mongodb")]
pub use mongo::{MongoStore, MongoStoreConfig};
#[cfg(feature = "mssql")]
pub use mssql::{MssqlStore, MssqlStoreConfig};
pub use noop::NoopStore;
#[cfg(feature = "redb")]
pub use redb::{RedbStore, RedbStoreConfig};
#[cfg(feature = "sql")]
pub use sql::{SqlPoolOptions, SqlStore, SqlStoreConfig};
#[derive(Debug, Error)]
pub enum StoreError {
#[error("store I/O error: {0}")]
Io(String),
#[error("store backend error: {0}")]
Backend(String),
}
#[async_trait]
pub trait MessageStore: Send + Sync {
async fn next_sender_seq(&self) -> Result<u64, StoreError>;
async fn next_target_seq(&self) -> Result<u64, StoreError>;
async fn set_next_sender_seq(&self, seq: u64) -> Result<(), StoreError>;
async fn set_next_target_seq(&self, seq: u64) -> Result<(), StoreError>;
async fn save(&self, seq: u64, message: &[u8]) -> Result<(), StoreError>;
async fn get(&self, begin: u64, end: u64) -> Result<Vec<(u64, Vec<u8>)>, StoreError>;
async fn reset(&self) -> Result<(), StoreError>;
fn was_corrupted(&self) -> bool {
false
}
async fn creation_time(&self) -> Result<Option<time::OffsetDateTime>, StoreError> {
Ok(None)
}
async fn save_and_advance_sender(&self, seq: u64, message: &[u8]) -> Result<(), StoreError> {
self.save(seq, message).await?;
self.set_next_sender_seq(seq + 1).await
}
async fn contains(&self, seq: u64) -> Result<bool, StoreError> {
let _ = seq;
Ok(false)
}
}
#[async_trait]
impl MessageStore for Arc<dyn MessageStore> {
async fn next_sender_seq(&self) -> Result<u64, StoreError> {
(**self).next_sender_seq().await
}
async fn next_target_seq(&self) -> Result<u64, StoreError> {
(**self).next_target_seq().await
}
async fn set_next_sender_seq(&self, seq: u64) -> Result<(), StoreError> {
(**self).set_next_sender_seq(seq).await
}
async fn set_next_target_seq(&self, seq: u64) -> Result<(), StoreError> {
(**self).set_next_target_seq(seq).await
}
async fn save(&self, seq: u64, message: &[u8]) -> Result<(), StoreError> {
(**self).save(seq, message).await
}
async fn get(&self, begin: u64, end: u64) -> Result<Vec<(u64, Vec<u8>)>, StoreError> {
(**self).get(begin, end).await
}
async fn reset(&self) -> Result<(), StoreError> {
(**self).reset().await
}
fn was_corrupted(&self) -> bool {
(**self).was_corrupted()
}
async fn creation_time(&self) -> Result<Option<time::OffsetDateTime>, StoreError> {
(**self).creation_time().await
}
async fn save_and_advance_sender(&self, seq: u64, message: &[u8]) -> Result<(), StoreError> {
(**self).save_and_advance_sender(seq, message).await
}
async fn contains(&self, seq: u64) -> Result<bool, StoreError> {
(**self).contains(seq).await
}
}
#[derive(Clone)]
pub enum StoreConfig {
Memory,
File {
dir: PathBuf,
options: FileStoreOptions,
},
CachedFile {
dir: PathBuf,
options: FileStoreOptions,
},
Noop,
#[cfg(feature = "sql")]
Sql {
url: String,
sessions_table: Option<String>,
messages_table: Option<String>,
session_id: Option<String>,
pool: Option<SqlPoolOptions>,
},
#[cfg(feature = "mssql")]
Mssql {
url: String,
sessions_table: Option<String>,
messages_table: Option<String>,
session_id: Option<String>,
},
#[cfg(feature = "redb")]
Redb {
path: PathBuf,
},
#[cfg(feature = "mongodb")]
Mongo {
uri: String,
},
Custom(Arc<dyn MessageStore>),
}
impl std::fmt::Debug for StoreConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Memory => write!(f, "Memory"),
Self::File { dir, options } => f
.debug_struct("File")
.field("dir", dir)
.field("options", options)
.finish(),
Self::CachedFile { dir, options } => f
.debug_struct("CachedFile")
.field("dir", dir)
.field("options", options)
.finish(),
Self::Noop => write!(f, "Noop"),
#[cfg(feature = "sql")]
Self::Sql { url, .. } => f.debug_struct("Sql").field("url", url).finish(),
#[cfg(feature = "mssql")]
Self::Mssql { url, .. } => f.debug_struct("Mssql").field("url", url).finish(),
#[cfg(feature = "redb")]
Self::Redb { path } => f.debug_struct("Redb").field("path", path).finish(),
#[cfg(feature = "mongodb")]
Self::Mongo { uri } => f.debug_struct("Mongo").field("uri", uri).finish(),
Self::Custom(_) => f
.debug_tuple("Custom")
.field(&"<dyn MessageStore>")
.finish(),
}
}
}
pub async fn build_store(config: &StoreConfig) -> Result<Box<dyn MessageStore>, StoreError> {
Ok(match config {
StoreConfig::Memory => Box::new(MemoryStore::new()),
StoreConfig::File { dir, options } => {
Box::new(FileStore::open_with_options(dir, *options)?)
}
StoreConfig::CachedFile { dir, options } => {
Box::new(CachedFileStore::open_with_options(dir, *options)?)
}
StoreConfig::Noop => Box::new(NoopStore::new()),
#[cfg(feature = "sql")]
StoreConfig::Sql {
url,
sessions_table,
messages_table,
session_id,
pool,
} => {
let defaults = SqlStoreConfig::new(url);
Box::new(
SqlStore::connect_with_config(SqlStoreConfig {
sessions_table: sessions_table.clone().unwrap_or(defaults.sessions_table),
messages_table: messages_table.clone().unwrap_or(defaults.messages_table),
session_id: session_id.clone().unwrap_or(defaults.session_id),
pool: pool.unwrap_or(defaults.pool),
..defaults
})
.await?,
)
}
#[cfg(feature = "mssql")]
StoreConfig::Mssql {
url,
sessions_table,
messages_table,
session_id,
} => {
let defaults = MssqlStoreConfig::new(url);
Box::new(
MssqlStore::connect_with_config(MssqlStoreConfig {
sessions_table: sessions_table.clone().unwrap_or(defaults.sessions_table),
messages_table: messages_table.clone().unwrap_or(defaults.messages_table),
session_id: session_id.clone().unwrap_or(defaults.session_id),
..defaults
})
.await?,
)
}
#[cfg(feature = "redb")]
StoreConfig::Redb { path } => Box::new(RedbStore::connect(path).await?),
#[cfg(feature = "mongodb")]
StoreConfig::Mongo { uri } => Box::new(MongoStore::connect(uri).await?),
StoreConfig::Custom(store) => Box::new(store.clone()),
})
}