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    managed::{CreatePoolError, PoolConfig},
10    Runtime,
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!("At least one of the following features must be enabled: core, replication, remote, sync"),
134        }
135    }
136}
137
138#[cfg(feature = "core")]
139#[derive(Clone, Debug)]
140#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
141#[allow(missing_docs)]
142pub struct Local {
143    pub path: PathBuf,
144    pub encryption_config: Option<EncryptionConfig>,
145    pub flags: Option<OpenFlags>,
146}
147
148#[cfg(feature = "core")]
149impl Local {
150    async fn libsql_database(&self) -> Result<libsql::Database, libsql::Error> {
151        let mut builder = Builder::new_local(&self.path);
152        if let Some(encryption_config) = &self.encryption_config {
153            builder = builder.encryption_config(encryption_config.to_libsql());
154        }
155        if let Some(flags) = &self.flags {
156            builder = builder.flags(flags.to_libsql());
157        }
158        builder.build().await
159    }
160}
161
162#[cfg(any(feature = "core", feature = "replication"))]
163#[derive(Clone, Debug)]
164#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
165#[allow(missing_docs)]
166pub struct EncryptionConfig {
167    pub cipher: Cipher,
168    pub encryption_key: bytes::Bytes,
169}
170
171#[cfg(feature = "core")]
172impl EncryptionConfig {
173    fn to_libsql(&self) -> libsql::EncryptionConfig {
174        libsql::EncryptionConfig {
175            cipher: self.cipher.to_libsql(),
176            encryption_key: self.encryption_key.clone(),
177        }
178    }
179}
180
181#[cfg(any(feature = "core", feature = "replication"))]
182#[derive(Clone, Copy, Debug, Default)]
183#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
184/// This is a 1:1 copy of [libsql::Cipher] with (de)serialization support
185pub enum Cipher {
186    #[default]
187    #[cfg_attr(feature = "serde", serde(rename = "aes256cbc"))]
188    /// AES 256 Bit CBC - No HMAC (wxSQLite3)
189    Aes256Cbc,
190}
191
192#[cfg(feature = "core")]
193impl Cipher {
194    fn to_libsql(self) -> libsql::Cipher {
195        match self {
196            Self::Aes256Cbc => libsql::Cipher::Aes256Cbc,
197        }
198    }
199}
200
201#[cfg(any(feature = "core", feature = "replication"))]
202#[derive(Copy, Clone, Debug)]
203#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
204#[allow(missing_docs)]
205pub struct OpenFlags {
206    pub read_only: bool,
207    pub read_write: bool,
208    pub create: bool,
209}
210
211#[cfg(any(feature = "core", feature = "replication"))]
212impl OpenFlags {
213    fn to_libsql(self) -> libsql::OpenFlags {
214        (if self.read_only {
215            libsql::OpenFlags::SQLITE_OPEN_READ_ONLY
216        } else {
217            libsql::OpenFlags::empty()
218        }) | (if self.read_write {
219            libsql::OpenFlags::SQLITE_OPEN_READ_WRITE
220        } else {
221            libsql::OpenFlags::empty()
222        }) | (if self.create {
223            libsql::OpenFlags::SQLITE_OPEN_CREATE
224        } else {
225            libsql::OpenFlags::empty()
226        })
227    }
228}
229
230#[cfg(feature = "replication")]
231#[derive(Clone, Debug)]
232#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
233#[allow(missing_docs)]
234pub struct LocalReplica {
235    pub path: PathBuf,
236    pub encryption_config: Option<EncryptionConfig>,
237    pub flags: Option<OpenFlags>,
238}
239
240#[cfg(feature = "replication")]
241impl LocalReplica {
242    async fn libsql_database(&self) -> Result<libsql::Database, libsql::Error> {
243        let mut builder = Builder::new_local_replica(&self.path);
244        if let Some(flags) = &self.flags {
245            builder = builder.flags(flags.to_libsql());
246        }
247        // FIXME add support for http_request_callback ?
248        builder.build().await
249    }
250}
251
252#[cfg(feature = "remote")]
253#[derive(Clone, Debug)]
254#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
255#[allow(missing_docs)]
256pub struct Remote {
257    pub url: String,
258    pub auth_token: String,
259    pub namespace: Option<String>,
260    pub remote_encryption: Option<EncryptionContext>,
261}
262
263#[cfg(feature = "remote")]
264impl Remote {
265    async fn libsql_database(&self) -> Result<libsql::Database, libsql::Error> {
266        let mut builder = Builder::new_remote(self.url.clone(), self.auth_token.clone());
267        // TODO connector
268        if let Some(namespace) = &self.namespace {
269            builder = builder.namespace(namespace);
270        }
271        #[allow(unused)]
272        if let Some(encryption_context) = &self.remote_encryption {
273            #[cfg(feature = "sync")]
274            {
275                builder = builder.remote_encryption(encryption_context.to_libsql());
276            }
277            #[cfg(not(feature = "sync"))]
278            return Err(libsql::Error::Misuse(
279                "Remote encryption unavailable: sync feature of libsql is disabled".into(),
280            ));
281        }
282        builder.build().await
283    }
284}
285
286#[cfg(feature = "replication")]
287#[derive(Clone, Debug)]
288#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
289#[allow(missing_docs)]
290pub struct RemoteReplica {
291    pub path: PathBuf,
292    pub url: String,
293    pub auth_token: String,
294    // TODO connector
295    pub encryption_config: Option<EncryptionConfig>,
296    // TODO http_request_callback
297    pub namespace: Option<String>,
298    pub read_your_writes: Option<bool>,
299    pub remote_encryption: Option<EncryptionContext>,
300    pub sync_interval: Option<Duration>,
301    pub sync_protocol: Option<SyncProtocol>,
302}
303
304#[cfg(feature = "replication")]
305impl RemoteReplica {
306    async fn libsql_database(&self) -> Result<libsql::Database, libsql::Error> {
307        // connector, namespace, remote_encryption
308        let mut builder =
309            Builder::new_remote_replica(&self.path, self.url.clone(), self.auth_token.clone());
310        // FIXME add support for connector
311        #[allow(unused)]
312        if let Some(encryption_config) = &self.encryption_config {
313            #[cfg(feature = "core")]
314            {
315                builder = builder.encryption_config(encryption_config.to_libsql());
316            }
317            #[cfg(not(feature = "core"))]
318            return Err(libsql::Error::Misuse("RemoteReplicate::encryption_config unavailable: core feature of libsql is disabled".into()));
319        }
320        // FIXME add support for http_request_callback ?
321        if let Some(namespace) = &self.namespace {
322            builder = builder.namespace(namespace);
323        }
324        if let Some(read_your_writes) = self.read_your_writes {
325            builder = builder.read_your_writes(read_your_writes);
326        }
327        #[allow(unused)]
328        if let Some(encryption_context) = &self.remote_encryption {
329            #[cfg(feature = "sync")]
330            {
331                builder = builder.remote_encryption(encryption_context.to_libsql());
332            }
333            #[cfg(not(feature = "sync"))]
334            return Err(libsql::Error::Misuse("RemoteReplication::encryption_context unavailable: sync feature of libsql is disabled".into()));
335        }
336        if let Some(sync_interval) = &self.sync_interval {
337            builder = builder.sync_interval(*sync_interval);
338        }
339        #[allow(unused)]
340        if let Some(sync_protocol) = &self.sync_protocol {
341            #[cfg(feature = "sync")]
342            {
343                builder = builder.sync_protocol(sync_protocol.to_libsql());
344            }
345            #[cfg(not(feature = "sync"))]
346            return Err(libsql::Error::Misuse(
347                "RemoteReplication::sync_protocol unavailable: sync feature of libsql is disabled"
348                    .into(),
349            ));
350        }
351        builder.build().await
352    }
353}
354
355#[cfg(any(feature = "remote", feature = "replication", feature = "sync"))]
356#[derive(Clone, Debug)]
357#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
358/// This is a 1:1 copy of [libsql::EncryptionContext] with (de)serialization support
359pub struct EncryptionContext {
360    /// The base64-encoded key for the encryption, sent on every request.
361    pub key: EncryptionKey,
362}
363
364#[cfg(feature = "sync")]
365impl EncryptionContext {
366    #[cfg(feature = "sync")]
367    fn to_libsql(&self) -> libsql::EncryptionContext {
368        libsql::EncryptionContext {
369            key: self.key.to_libsql(),
370        }
371    }
372}
373
374#[cfg(any(feature = "remote", feature = "replication", feature = "sync"))]
375#[derive(Clone, Debug)]
376#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
377/// This is a 1:1 copy of [libsql::EncryptionKey] with (de)serialization support
378pub enum EncryptionKey {
379    /// The key is a base64-encoded string.
380    Base64Encoded(String),
381    /// The key is a byte array.
382    Bytes(Vec<u8>),
383}
384
385#[cfg(any(feature = "remote", feature = "sync"))]
386impl EncryptionKey {
387    #[cfg(feature = "sync")]
388    fn to_libsql(&self) -> libsql::EncryptionKey {
389        #[cfg(feature = "sync")]
390        match self {
391            Self::Base64Encoded(string) => libsql::EncryptionKey::Base64Encoded(string.clone()),
392            Self::Bytes(bytes) => libsql::EncryptionKey::Bytes(bytes.clone()),
393        }
394    }
395}
396
397#[cfg(feature = "replication")]
398#[derive(Clone, Copy, Debug)]
399#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
400/// This is a 1:1 copy of the [libsql::SyncProtocol] with (de)serialization support
401pub enum SyncProtocol {
402    #[allow(missing_docs)]
403    V1,
404    #[allow(missing_docs)]
405    V2,
406}
407
408#[cfg(all(feature = "replication", feature = "sync"))]
409impl SyncProtocol {
410    fn to_libsql(self) -> libsql::SyncProtocol {
411        match self {
412            Self::V1 => libsql::SyncProtocol::V1,
413            Self::V2 => libsql::SyncProtocol::V2,
414        }
415    }
416}
417
418#[cfg(feature = "sync")]
419#[derive(Clone, Debug)]
420#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
421#[allow(missing_docs)]
422pub struct SyncedDatabase {
423    pub path: PathBuf,
424    pub url: String,
425    pub auth_token: String,
426    // TODO connector
427    pub read_your_writes: Option<bool>,
428    pub remote_encryption: Option<EncryptionContext>,
429    pub remote_writes: Option<bool>,
430    pub set_push_batch_size: Option<u32>,
431    pub sync_interval: Option<Duration>,
432}
433
434#[cfg(feature = "sync")]
435impl SyncedDatabase {
436    async fn libsql_database(&self) -> Result<libsql::Database, libsql::Error> {
437        let mut builder =
438            Builder::new_synced_database(&self.path, self.url.clone(), self.auth_token.clone());
439        // TODO connector
440        if let Some(read_your_writes) = self.read_your_writes {
441            builder = builder.read_your_writes(read_your_writes);
442        }
443        if let Some(encryption_context) = &self.remote_encryption {
444            builder = builder.remote_encryption(encryption_context.to_libsql());
445        }
446        if let Some(remote_writes) = &self.remote_writes {
447            builder = builder.remote_writes(*remote_writes);
448        }
449        if let Some(push_batch_size) = &self.set_push_batch_size {
450            builder = builder.set_push_batch_size(*push_batch_size);
451        }
452        if let Some(sync_interval) = &self.sync_interval {
453            builder = builder.sync_interval(*sync_interval);
454        }
455        builder.build().await
456    }
457}
458
459/// This error is returned if there is something wrong with the libSQL configuration.
460pub type ConfigError = libsql::Error;