tsuzuri_libsql/
read.rs

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 encoded key (64 chars = 32 bytes)
68                hex::decode(&encryption_key)
69                    .map_err(|e| libsql::Error::ConnectionFailed(format!("Invalid hex in encryption key: {}", e)))?
70            } else {
71                // Raw string key (should be 32 bytes)
72                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}