Skip to main content

deadpool_libsql/
config.rs

1//! This module contains all the configuration structures
2
3#[cfg(any(feature = "core", feature = "replication", feature = "sync"))]
4use std::path::PathBuf;
5#[cfg(any(feature = "replication", feature = "sync"))]
6use std::time::Duration;
7
8use deadpool::{
9    Runtime,
10    managed::{CreatePoolError, PoolConfig},
11};
12use libsql::Builder;
13#[cfg(feature = "serde")]
14use serde::{Deserialize, Serialize};
15
16use crate::{Manager, Pool, PoolBuilder};
17
18/// Configuration object.
19///
20/// # Example (from environment)
21///
22/// By enabling the `serde` feature you can read the configuration using the
23/// [`config`](https://crates.io/crates/config) crate as following:
24/// ```env
25/// LIBSQL__DATABASE=Local
26/// LIBSQL__PATH=db.sqlite
27/// ```
28/// ```rust
29/// #[derive(serde::Deserialize, serde::Serialize)]
30/// struct Config {
31///     libsql: deadpool_libsql::config::Config,
32/// }
33/// impl Config {
34///     pub fn from_env() -> Result<Self, config::ConfigError> {
35///         let mut cfg = config::Config::builder()
36///            .add_source(config::Environment::default().separator("__"))
37///            .build()?;
38///            cfg.try_deserialize()
39///     }
40/// }
41/// ```
42#[derive(Clone, Debug)]
43#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
44pub struct Config {
45    /// Database configuration.
46    #[cfg_attr(feature = "serde", serde(flatten))]
47    pub database: Database,
48    /// Pool configuration.
49    #[cfg_attr(feature = "serde", serde(default))]
50    pub pool: PoolConfig,
51}
52
53impl Config {
54    /// Create a new [`Config`] with the given database
55    #[must_use]
56    pub fn new(database: Database) -> Self {
57        Self {
58            database,
59            pool: PoolConfig::default(),
60        }
61    }
62
63    /// Create a new [`Pool`] using this [`Config`].
64    ///
65    /// # Errors
66    ///
67    /// See [`CreatePoolError`] for details.
68    pub async fn create_pool(
69        self,
70        runtime: Option<Runtime>,
71    ) -> Result<Pool, CreatePoolError<ConfigError>> {
72        let mut builder = self.builder().await.map_err(CreatePoolError::Config)?;
73        if let Some(runtime) = runtime {
74            builder = builder.runtime(runtime);
75        }
76        builder.build().map_err(CreatePoolError::Build)
77    }
78
79    /// Creates a new [`PoolBuilder`] using this [`Config`].
80    ///
81    /// # Errors
82    ///
83    /// See [`ConfigError`] for details.
84    pub async fn builder(self) -> Result<PoolBuilder, ConfigError> {
85        let config = self.pool;
86        let manager = Manager::from_config(self).await?;
87        Ok(Pool::builder(manager).config(config))
88    }
89}
90
91#[derive(Clone, Debug)]
92#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
93#[cfg_attr(feature = "serde", serde(tag = "database"))]
94/// This is a 1:1 mapping of [libsql::Builder] to a (de)serializable
95/// config structure
96pub enum Database {
97    /// See: [libsql::Builder::new_local]
98    #[cfg(feature = "core")]
99    Local(Local),
100    /// See: [libsql::Builder::new_local_replica]
101    #[cfg(feature = "replication")]
102    LocalReplica(LocalReplica),
103    /// See: [libsql::Builder::new_remote]
104    #[cfg(feature = "remote")]
105    Remote(Remote),
106    /// See: [libsql::Builder::new_remote_replica]
107    #[cfg(feature = "replication")]
108    RemoteReplica(RemoteReplica),
109    /// See: [libsql::Builder::new_synced_database]
110    #[cfg(feature = "sync")]
111    SyncedDatabase(SyncedDatabase),
112}
113
114impl Database {
115    pub(crate) async fn libsql_database(&self) -> Result<libsql::Database, libsql::Error> {
116        match self {
117            #[cfg(feature = "core")]
118            Self::Local(x) => x.libsql_database().await,
119            #[cfg(feature = "replication")]
120            Self::LocalReplica(x) => x.libsql_database().await,
121            #[cfg(feature = "remote")]
122            Self::Remote(x) => x.libsql_database().await,
123            #[cfg(feature = "replication")]
124            Self::RemoteReplica(x) => x.libsql_database().await,
125            #[cfg(feature = "sync")]
126            Self::SyncedDatabase(x) => x.libsql_database().await,
127            #[cfg(not(any(
128                feature = "core",
129                feature = "replication",
130                feature = "remote",
131                feature = "sync"
132            )))]
133            _ => compile_error!(
134                "At least one of the following features must be enabled: core, replication, remote, sync"
135            ),
136        }
137    }
138}
139
140#[cfg(feature = "core")]
141#[derive(Clone, Debug)]
142#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
143#[allow(missing_docs)]
144pub struct Local {
145    pub path: PathBuf,
146    pub encryption_config: Option<EncryptionConfig>,
147    pub flags: Option<OpenFlags>,
148}
149
150#[cfg(feature = "core")]
151impl Local {
152    async fn libsql_database(&self) -> Result<libsql::Database, libsql::Error> {
153        let mut builder = Builder::new_local(&self.path);
154        if let Some(encryption_config) = &self.encryption_config {
155            builder = builder.encryption_config(encryption_config.to_libsql());
156        }
157        if let Some(flags) = &self.flags {
158            builder = builder.flags(flags.to_libsql());
159        }
160        builder.build().await
161    }
162}
163
164#[cfg(any(feature = "core", feature = "replication"))]
165#[derive(Clone, Debug)]
166#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
167#[allow(missing_docs)]
168pub struct EncryptionConfig {
169    pub cipher: Cipher,
170    pub encryption_key: bytes::Bytes,
171}
172
173#[cfg(feature = "core")]
174impl EncryptionConfig {
175    fn to_libsql(&self) -> libsql::EncryptionConfig {
176        libsql::EncryptionConfig {
177            cipher: self.cipher.to_libsql(),
178            encryption_key: self.encryption_key.clone(),
179        }
180    }
181}
182
183#[cfg(any(feature = "core", feature = "replication"))]
184#[derive(Clone, Copy, Debug, Default)]
185#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
186/// This is a 1:1 copy of [libsql::Cipher] with (de)serialization support
187pub enum Cipher {
188    #[default]
189    #[cfg_attr(feature = "serde", serde(rename = "aes256cbc"))]
190    /// AES 256 Bit CBC - No HMAC (wxSQLite3)
191    Aes256Cbc,
192}
193
194#[cfg(feature = "core")]
195impl Cipher {
196    fn to_libsql(self) -> libsql::Cipher {
197        match self {
198            Self::Aes256Cbc => libsql::Cipher::Aes256Cbc,
199        }
200    }
201}
202
203#[cfg(any(feature = "core", feature = "replication"))]
204#[derive(Copy, Clone, Debug)]
205#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
206#[allow(missing_docs)]
207pub struct OpenFlags {
208    pub read_only: bool,
209    pub read_write: bool,
210    pub create: bool,
211}
212
213#[cfg(any(feature = "core", feature = "replication"))]
214impl OpenFlags {
215    fn to_libsql(self) -> libsql::OpenFlags {
216        (if self.read_only {
217            libsql::OpenFlags::SQLITE_OPEN_READ_ONLY
218        } else {
219            libsql::OpenFlags::empty()
220        }) | (if self.read_write {
221            libsql::OpenFlags::SQLITE_OPEN_READ_WRITE
222        } else {
223            libsql::OpenFlags::empty()
224        }) | (if self.create {
225            libsql::OpenFlags::SQLITE_OPEN_CREATE
226        } else {
227            libsql::OpenFlags::empty()
228        })
229    }
230}
231
232#[cfg(feature = "replication")]
233#[derive(Clone, Debug)]
234#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
235#[allow(missing_docs)]
236pub struct LocalReplica {
237    pub path: PathBuf,
238    pub encryption_config: Option<EncryptionConfig>,
239    pub flags: Option<OpenFlags>,
240}
241
242#[cfg(feature = "replication")]
243impl LocalReplica {
244    async fn libsql_database(&self) -> Result<libsql::Database, libsql::Error> {
245        let mut builder = Builder::new_local_replica(&self.path);
246        if let Some(flags) = &self.flags {
247            builder = builder.flags(flags.to_libsql());
248        }
249        // FIXME add support for http_request_callback ?
250        builder.build().await
251    }
252}
253
254#[cfg(feature = "remote")]
255#[derive(Clone, Debug)]
256#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
257#[allow(missing_docs)]
258pub struct Remote {
259    pub url: String,
260    pub auth_token: String,
261    pub namespace: Option<String>,
262    pub remote_encryption: Option<EncryptionContext>,
263}
264
265#[cfg(feature = "remote")]
266impl Remote {
267    async fn libsql_database(&self) -> Result<libsql::Database, libsql::Error> {
268        let mut builder = Builder::new_remote(self.url.clone(), self.auth_token.clone());
269        // TODO connector
270        if let Some(namespace) = &self.namespace {
271            builder = builder.namespace(namespace);
272        }
273        #[allow(unused)]
274        if let Some(encryption_context) = &self.remote_encryption {
275            #[cfg(feature = "sync")]
276            {
277                builder = builder.remote_encryption(encryption_context.to_libsql());
278            }
279            #[cfg(not(feature = "sync"))]
280            return Err(libsql::Error::Misuse(
281                "Remote encryption unavailable: sync feature of libsql is disabled".into(),
282            ));
283        }
284        builder.build().await
285    }
286}
287
288#[cfg(feature = "replication")]
289#[derive(Clone, Debug)]
290#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
291#[allow(missing_docs)]
292pub struct RemoteReplica {
293    pub path: PathBuf,
294    pub url: String,
295    pub auth_token: String,
296    // TODO connector
297    pub encryption_config: Option<EncryptionConfig>,
298    // TODO http_request_callback
299    pub namespace: Option<String>,
300    pub read_your_writes: Option<bool>,
301    pub remote_encryption: Option<EncryptionContext>,
302    pub sync_interval: Option<Duration>,
303    pub sync_protocol: Option<SyncProtocol>,
304}
305
306#[cfg(feature = "replication")]
307impl RemoteReplica {
308    async fn libsql_database(&self) -> Result<libsql::Database, libsql::Error> {
309        // connector, namespace, remote_encryption
310        let mut builder =
311            Builder::new_remote_replica(&self.path, self.url.clone(), self.auth_token.clone());
312        // FIXME add support for connector
313        #[allow(unused)]
314        if let Some(encryption_config) = &self.encryption_config {
315            #[cfg(feature = "core")]
316            {
317                builder = builder.encryption_config(encryption_config.to_libsql());
318            }
319            #[cfg(not(feature = "core"))]
320            return Err(libsql::Error::Misuse("RemoteReplicate::encryption_config unavailable: core feature of libsql is disabled".into()));
321        }
322        // FIXME add support for http_request_callback ?
323        if let Some(namespace) = &self.namespace {
324            builder = builder.namespace(namespace);
325        }
326        if let Some(read_your_writes) = self.read_your_writes {
327            builder = builder.read_your_writes(read_your_writes);
328        }
329        #[allow(unused)]
330        if let Some(encryption_context) = &self.remote_encryption {
331            #[cfg(feature = "sync")]
332            {
333                builder = builder.remote_encryption(encryption_context.to_libsql());
334            }
335            #[cfg(not(feature = "sync"))]
336            return Err(libsql::Error::Misuse("RemoteReplication::encryption_context unavailable: sync feature of libsql is disabled".into()));
337        }
338        if let Some(sync_interval) = &self.sync_interval {
339            builder = builder.sync_interval(*sync_interval);
340        }
341        #[allow(unused)]
342        if let Some(sync_protocol) = &self.sync_protocol {
343            #[cfg(feature = "sync")]
344            {
345                builder = builder.sync_protocol(sync_protocol.to_libsql());
346            }
347            #[cfg(not(feature = "sync"))]
348            return Err(libsql::Error::Misuse(
349                "RemoteReplication::sync_protocol unavailable: sync feature of libsql is disabled"
350                    .into(),
351            ));
352        }
353        builder.build().await
354    }
355}
356
357#[cfg(any(feature = "remote", feature = "replication", feature = "sync"))]
358#[derive(Clone, Debug)]
359#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
360/// This is a 1:1 copy of [libsql::EncryptionContext] with (de)serialization support
361pub struct EncryptionContext {
362    /// The base64-encoded key for the encryption, sent on every request.
363    pub key: EncryptionKey,
364}
365
366#[cfg(feature = "sync")]
367impl EncryptionContext {
368    #[cfg(feature = "sync")]
369    fn to_libsql(&self) -> libsql::EncryptionContext {
370        libsql::EncryptionContext {
371            key: self.key.to_libsql(),
372        }
373    }
374}
375
376#[cfg(any(feature = "remote", feature = "replication", feature = "sync"))]
377#[derive(Clone, Debug)]
378#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
379/// This is a 1:1 copy of [libsql::EncryptionKey] with (de)serialization support
380pub enum EncryptionKey {
381    /// The key is a base64-encoded string.
382    Base64Encoded(String),
383    /// The key is a byte array.
384    Bytes(Vec<u8>),
385}
386
387#[cfg(any(feature = "remote", feature = "sync"))]
388impl EncryptionKey {
389    #[cfg(feature = "sync")]
390    fn to_libsql(&self) -> libsql::EncryptionKey {
391        #[cfg(feature = "sync")]
392        match self {
393            Self::Base64Encoded(string) => libsql::EncryptionKey::Base64Encoded(string.clone()),
394            Self::Bytes(bytes) => libsql::EncryptionKey::Bytes(bytes.clone()),
395        }
396    }
397}
398
399#[cfg(feature = "replication")]
400#[derive(Clone, Copy, Debug)]
401#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
402/// This is a 1:1 copy of the [libsql::SyncProtocol] with (de)serialization support
403pub enum SyncProtocol {
404    #[allow(missing_docs)]
405    V1,
406    #[allow(missing_docs)]
407    V2,
408}
409
410#[cfg(all(feature = "replication", feature = "sync"))]
411impl SyncProtocol {
412    fn to_libsql(self) -> libsql::SyncProtocol {
413        match self {
414            Self::V1 => libsql::SyncProtocol::V1,
415            Self::V2 => libsql::SyncProtocol::V2,
416        }
417    }
418}
419
420#[cfg(feature = "sync")]
421#[derive(Clone, Debug)]
422#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
423#[allow(missing_docs)]
424pub struct SyncedDatabase {
425    pub path: PathBuf,
426    pub url: String,
427    pub auth_token: String,
428    // TODO connector
429    pub read_your_writes: Option<bool>,
430    pub remote_encryption: Option<EncryptionContext>,
431    pub remote_writes: Option<bool>,
432    pub set_push_batch_size: Option<u32>,
433    pub sync_interval: Option<Duration>,
434}
435
436#[cfg(feature = "sync")]
437impl SyncedDatabase {
438    async fn libsql_database(&self) -> Result<libsql::Database, libsql::Error> {
439        let mut builder =
440            Builder::new_synced_database(&self.path, self.url.clone(), self.auth_token.clone());
441        // TODO connector
442        if let Some(read_your_writes) = self.read_your_writes {
443            builder = builder.read_your_writes(read_your_writes);
444        }
445        if let Some(encryption_context) = &self.remote_encryption {
446            builder = builder.remote_encryption(encryption_context.to_libsql());
447        }
448        if let Some(remote_writes) = &self.remote_writes {
449            builder = builder.remote_writes(*remote_writes);
450        }
451        if let Some(push_batch_size) = &self.set_push_batch_size {
452            builder = builder.set_push_batch_size(*push_batch_size);
453        }
454        if let Some(sync_interval) = &self.sync_interval {
455            builder = builder.sync_interval(*sync_interval);
456        }
457        builder.build().await
458    }
459}
460
461/// This error is returned if there is something wrong with the libSQL configuration.
462pub type ConfigError = libsql::Error;