use super::storage::{InMemoryStorage, StorageBackend};
use std::error::Error;
use std::fmt;
#[cfg(feature = "postgres-backend")]
pub mod postgres;
#[cfg(feature = "d1-backend")]
pub mod d1;
#[cfg(feature = "postgres-backend")]
pub use postgres::PostgresIncrementalBackend;
#[cfg(feature = "d1-backend")]
pub use d1::D1IncrementalBackend;
#[derive(Debug)]
pub enum IncrementalError {
UnsupportedBackend(&'static str),
InitializationFailed(String),
Storage(super::storage::StorageError),
}
impl fmt::Display for IncrementalError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IncrementalError::UnsupportedBackend(backend) => {
write!(
f,
"Backend '{}' is not available. Enable the corresponding feature flag.",
backend
)
}
IncrementalError::InitializationFailed(msg) => {
write!(f, "Backend initialization failed: {}", msg)
}
IncrementalError::Storage(err) => write!(f, "Storage error: {}", err),
}
}
}
impl Error for IncrementalError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
IncrementalError::Storage(err) => Some(err),
_ => None,
}
}
}
impl From<super::storage::StorageError> for IncrementalError {
fn from(err: super::storage::StorageError) -> Self {
IncrementalError::Storage(err)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackendType {
Postgres,
D1,
InMemory,
}
#[derive(Debug, Clone)]
pub enum BackendConfig {
Postgres {
database_url: String,
},
D1 {
account_id: String,
database_id: String,
api_token: String,
},
InMemory,
}
pub async fn create_backend(
backend_type: BackendType,
config: BackendConfig,
) -> Result<Box<dyn StorageBackend>, IncrementalError> {
match (backend_type, config) {
(BackendType::Postgres, BackendConfig::Postgres { database_url }) => {
#[cfg(feature = "postgres-backend")]
{
PostgresIncrementalBackend::new(&database_url)
.await
.map(|b| Box::new(b) as Box<dyn StorageBackend>)
.map_err(|e| {
IncrementalError::InitializationFailed(format!(
"Postgres init failed: {}",
e
))
})
}
#[cfg(not(feature = "postgres-backend"))]
{
let _ = database_url; Err(IncrementalError::UnsupportedBackend("postgres"))
}
}
(
BackendType::D1,
BackendConfig::D1 {
account_id,
database_id,
api_token,
},
) => {
#[cfg(feature = "d1-backend")]
{
D1IncrementalBackend::new(account_id, database_id, api_token)
.map(|b| Box::new(b) as Box<dyn StorageBackend>)
.map_err(|e| {
IncrementalError::InitializationFailed(format!("D1 init failed: {}", e))
})
}
#[cfg(not(feature = "d1-backend"))]
{
let _ = (account_id, database_id, api_token); Err(IncrementalError::UnsupportedBackend("d1"))
}
}
(BackendType::InMemory, BackendConfig::InMemory) => {
Ok(Box::new(InMemoryStorage::new()) as Box<dyn StorageBackend>)
}
_ => Err(IncrementalError::InitializationFailed(
"Backend type and configuration mismatch".to_string(),
)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_create_in_memory_backend() {
let result = create_backend(BackendType::InMemory, BackendConfig::InMemory).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_configuration_mismatch() {
let result = create_backend(
BackendType::InMemory,
BackendConfig::Postgres {
database_url: "test".to_string(),
},
)
.await;
assert!(result.is_err());
if let Err(err) = result {
assert!(matches!(err, IncrementalError::InitializationFailed(_)));
}
}
#[cfg(not(feature = "postgres-backend"))]
#[tokio::test]
async fn test_postgres_backend_unavailable() {
let result = create_backend(
BackendType::Postgres,
BackendConfig::Postgres {
database_url: "postgresql://localhost/test".to_string(),
},
)
.await;
assert!(result.is_err());
if let Err(err) = result {
assert!(matches!(
err,
IncrementalError::UnsupportedBackend("postgres")
));
}
}
#[cfg(not(feature = "d1-backend"))]
#[tokio::test]
async fn test_d1_backend_unavailable() {
let result = create_backend(
BackendType::D1,
BackendConfig::D1 {
account_id: "test".to_string(),
database_id: "test".to_string(),
api_token: "test".to_string(),
},
)
.await;
assert!(result.is_err());
if let Err(err) = result {
assert!(matches!(err, IncrementalError::UnsupportedBackend("d1")));
}
}
#[test]
fn test_incremental_error_display() {
let err = IncrementalError::UnsupportedBackend("test");
assert!(format!("{}", err).contains("not available"));
let err = IncrementalError::InitializationFailed("connection failed".to_string());
assert!(format!("{}", err).contains("connection failed"));
}
#[test]
fn test_backend_type_equality() {
assert_eq!(BackendType::InMemory, BackendType::InMemory);
assert_ne!(BackendType::Postgres, BackendType::D1);
}
}