1use crate::config::LibSqlConfig;
2use bytes::Bytes;
3use libsql::{Builder, Cipher, Connection, Database, EncryptionConfig};
4use std::time::Duration;
5
6#[derive(Debug, Clone)]
7pub struct RemoteConfig {
8 pub url: String,
9 pub auth_token: String,
10}
11
12#[derive(Debug, Clone)]
13pub struct EmbeddedReplicaConfig {
14 pub local_path: String,
15 pub sync_url: String,
16 pub auth_token: String,
17 pub sync_interval: Option<Duration>,
18 pub encryption_key: Option<String>,
19}
20
21#[derive(Debug, Clone)]
22pub enum ConnectionConfig {
23 Remote(RemoteConfig),
24 EmbeddedReplica(EmbeddedReplicaConfig),
25}
26
27#[derive(Debug)]
28pub enum ConnectionType {
29 Remote(Connection),
30 EmbeddedReplica { connection: Connection, database: Database },
31}
32
33#[derive(Debug)]
34pub struct ConnectionManager {
35 connection_type: ConnectionType,
36}
37
38impl ConnectionManager {
39 pub async fn new(config: ConnectionConfig) -> Result<Self, libsql::Error> {
40 match config {
41 ConnectionConfig::Remote(remote_config) => Self::new_remote(remote_config).await,
42 ConnectionConfig::EmbeddedReplica(replica_config) => Self::new_embedded_replica(replica_config).await,
43 }
44 }
45
46 pub async fn from_config(config: LibSqlConfig) -> Result<Self, libsql::Error> {
47 Self::new(config.connection).await
48 }
49
50 pub async fn new_remote(config: RemoteConfig) -> Result<Self, libsql::Error> {
51 let db = Builder::new_remote(config.url, config.auth_token).build().await?;
52 let conn = db.connect()?;
53 Ok(Self {
54 connection_type: ConnectionType::Remote(conn),
55 })
56 }
57
58 pub async fn new_embedded_replica(config: EmbeddedReplicaConfig) -> Result<Self, libsql::Error> {
59 let mut builder = Builder::new_remote_replica(config.local_path, config.sync_url, config.auth_token);
60
61 if let Some(sync_interval) = config.sync_interval {
62 builder = builder.sync_interval(sync_interval);
63 }
64
65 if let Some(encryption_key) = config.encryption_key {
66 let key_bytes = if encryption_key.len() == 64 {
67 hex::decode(&encryption_key)
69 .map_err(|e| libsql::Error::ConnectionFailed(format!("Invalid hex in encryption key: {}", e)))?
70 } else {
71 encryption_key.into_bytes()
73 };
74
75 if key_bytes.len() != 32 {
76 return Err(libsql::Error::ConnectionFailed(
77 "Encryption key must be exactly 32 bytes (256 bits) for AES-256-CBC".to_string(),
78 ));
79 }
80
81 let encryption_config = EncryptionConfig::new(Cipher::Aes256Cbc, Bytes::from(key_bytes));
82 builder = builder.encryption_config(encryption_config);
83 }
84
85 let db = builder.build().await?;
86 let conn = db.connect()?;
87
88 Ok(Self {
89 connection_type: ConnectionType::EmbeddedReplica {
90 connection: conn,
91 database: db,
92 },
93 })
94 }
95
96 pub async fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
97 let config = LibSqlConfig::from_env()?;
98 Ok(Self::from_config(config).await?)
99 }
100
101 pub fn get_connection(&self) -> &Connection {
102 match &self.connection_type {
103 ConnectionType::Remote(conn) => conn,
104 ConnectionType::EmbeddedReplica { connection, .. } => connection,
105 }
106 }
107
108 pub async fn sync(&self) -> Result<(), libsql::Error> {
109 match &self.connection_type {
110 ConnectionType::Remote(_) => Ok(()),
111 ConnectionType::EmbeddedReplica { database, .. } => {
112 database.sync().await?;
113 Ok(())
114 }
115 }
116 }
117
118 pub fn is_embedded_replica(&self) -> bool {
119 matches!(self.connection_type, ConnectionType::EmbeddedReplica { .. })
120 }
121}