Skip to main content

bitwarden_state/sdk_managed/
mod.rs

1use std::sync::Arc;
2
3use bitwarden_error::bitwarden_error;
4use thiserror::Error;
5
6use crate::repository::{Repository, RepositoryError, RepositoryItem, RepositoryMigrations};
7
8mod configuration;
9pub use configuration::DatabaseConfiguration;
10
11#[cfg(target_arch = "wasm32")]
12mod indexed_db;
13
14#[cfg(not(target_arch = "wasm32"))]
15mod sqlite;
16
17mod memory;
18pub(super) use memory::MemoryDatabase;
19
20/// Errors that can occur when interacting with the SDK-managed database.
21#[bitwarden_error(flat)]
22#[derive(Debug, Error)]
23pub enum DatabaseError {
24    /// The requested database configuration is not supported on the current platform.
25    #[error("Database not supported on this platform: {0:?}")]
26    UnsupportedConfiguration(DatabaseConfiguration),
27
28    /// A call dispatched through the thread-bound runner failed.
29    #[error(transparent)]
30    ThreadBoundRunner(#[from] bitwarden_threading::CallError),
31
32    /// Failed to serialize or deserialize a stored value.
33    #[error("Serialization error: {0}")]
34    Serialization(#[from] serde_json::Error),
35
36    /// A JavaScript error was raised by the IndexedDB backend.
37    #[error("JS error: {0}")]
38    JS(String),
39
40    /// An unexpected internal error occurred in the database backend.
41    #[error("Internal error: {0}")]
42    Internal(String),
43
44    /// The database has been closed (e.g. by [`crate::registry::StateRegistry::wipe`]) and
45    /// can no longer service operations.
46    #[error("Database is closed")]
47    Closed,
48}
49
50#[cfg(target_arch = "wasm32")]
51impl From<::indexed_db::Error<indexed_db::IndexedDbInternalError>> for DatabaseError {
52    fn from(e: ::indexed_db::Error<indexed_db::IndexedDbInternalError>) -> Self {
53        DatabaseError::Internal(e.to_string())
54    }
55}
56
57#[cfg(not(target_arch = "wasm32"))]
58impl From<rusqlite::Error> for DatabaseError {
59    fn from(e: rusqlite::Error) -> Self {
60        DatabaseError::Internal(e.to_string())
61    }
62}
63
64pub trait Database {
65    async fn initialize(
66        configuration: DatabaseConfiguration,
67        registrations: RepositoryMigrations,
68    ) -> Result<Self, DatabaseError>
69    where
70        Self: Sized;
71
72    async fn get<T: RepositoryItem>(&self, key: &str) -> Result<Option<T>, DatabaseError>;
73
74    async fn list<T: RepositoryItem>(&self) -> Result<Vec<T>, DatabaseError>;
75
76    async fn set<T: RepositoryItem>(&self, key: &str, value: T) -> Result<(), DatabaseError>;
77
78    async fn set_bulk<T: RepositoryItem>(
79        &self,
80        values: Vec<(String, T)>,
81    ) -> Result<(), DatabaseError>;
82
83    async fn remove<T: RepositoryItem>(&self, key: &str) -> Result<(), DatabaseError>;
84
85    async fn remove_bulk<T: RepositoryItem>(&self, keys: Vec<String>) -> Result<(), DatabaseError>;
86
87    async fn remove_all<T: RepositoryItem>(&self) -> Result<(), DatabaseError>;
88
89    async fn wipe(&self) -> Result<(), DatabaseError>;
90}
91
92#[derive(Clone)]
93pub(super) enum SystemDatabase {
94    #[cfg(not(target_arch = "wasm32"))]
95    Sqlite(sqlite::SqliteDatabase),
96    #[cfg(target_arch = "wasm32")]
97    IndexedDb(indexed_db::IndexedDbDatabase),
98    Memory(MemoryDatabase),
99}
100
101impl Database for SystemDatabase {
102    async fn initialize(
103        configuration: DatabaseConfiguration,
104        migrations: RepositoryMigrations,
105    ) -> Result<Self, DatabaseError> {
106        match configuration {
107            #[cfg(not(target_arch = "wasm32"))]
108            DatabaseConfiguration::Sqlite { .. } => Ok(SystemDatabase::Sqlite(
109                sqlite::SqliteDatabase::initialize(configuration, migrations).await?,
110            )),
111            #[cfg(target_arch = "wasm32")]
112            DatabaseConfiguration::IndexedDb { .. } => Ok(SystemDatabase::IndexedDb(
113                indexed_db::IndexedDbDatabase::initialize(configuration, migrations).await?,
114            )),
115            DatabaseConfiguration::Memory => Ok(SystemDatabase::Memory(MemoryDatabase::new())),
116            #[allow(unreachable_patterns)]
117            other => Err(DatabaseError::UnsupportedConfiguration(other)),
118        }
119    }
120
121    async fn get<T: RepositoryItem>(&self, key: &str) -> Result<Option<T>, DatabaseError> {
122        match self {
123            #[cfg(not(target_arch = "wasm32"))]
124            SystemDatabase::Sqlite(db) => db.get(key).await,
125            #[cfg(target_arch = "wasm32")]
126            SystemDatabase::IndexedDb(db) => db.get(key).await,
127            SystemDatabase::Memory(db) => db.get(key).await,
128        }
129    }
130
131    async fn list<T: RepositoryItem>(&self) -> Result<Vec<T>, DatabaseError> {
132        match self {
133            #[cfg(not(target_arch = "wasm32"))]
134            SystemDatabase::Sqlite(db) => db.list().await,
135            #[cfg(target_arch = "wasm32")]
136            SystemDatabase::IndexedDb(db) => db.list().await,
137            SystemDatabase::Memory(db) => db.list().await,
138        }
139    }
140
141    async fn set<T: RepositoryItem>(&self, key: &str, value: T) -> Result<(), DatabaseError> {
142        match self {
143            #[cfg(not(target_arch = "wasm32"))]
144            SystemDatabase::Sqlite(db) => db.set(key, value).await,
145            #[cfg(target_arch = "wasm32")]
146            SystemDatabase::IndexedDb(db) => db.set(key, value).await,
147            SystemDatabase::Memory(db) => db.set(key, value).await,
148        }
149    }
150
151    async fn set_bulk<T: RepositoryItem>(
152        &self,
153        values: Vec<(String, T)>,
154    ) -> Result<(), DatabaseError> {
155        match self {
156            #[cfg(not(target_arch = "wasm32"))]
157            SystemDatabase::Sqlite(db) => db.set_bulk(values).await,
158            #[cfg(target_arch = "wasm32")]
159            SystemDatabase::IndexedDb(db) => db.set_bulk(values).await,
160            SystemDatabase::Memory(db) => db.set_bulk(values).await,
161        }
162    }
163
164    async fn remove<T: RepositoryItem>(&self, key: &str) -> Result<(), DatabaseError> {
165        match self {
166            #[cfg(not(target_arch = "wasm32"))]
167            SystemDatabase::Sqlite(db) => db.remove::<T>(key).await,
168            #[cfg(target_arch = "wasm32")]
169            SystemDatabase::IndexedDb(db) => db.remove::<T>(key).await,
170            SystemDatabase::Memory(db) => db.remove::<T>(key).await,
171        }
172    }
173
174    async fn remove_bulk<T: RepositoryItem>(&self, keys: Vec<String>) -> Result<(), DatabaseError> {
175        match self {
176            #[cfg(not(target_arch = "wasm32"))]
177            SystemDatabase::Sqlite(db) => db.remove_bulk::<T>(keys).await,
178            #[cfg(target_arch = "wasm32")]
179            SystemDatabase::IndexedDb(db) => db.remove_bulk::<T>(keys).await,
180            SystemDatabase::Memory(db) => db.remove_bulk::<T>(keys).await,
181        }
182    }
183
184    async fn remove_all<T: RepositoryItem>(&self) -> Result<(), DatabaseError> {
185        match self {
186            #[cfg(not(target_arch = "wasm32"))]
187            SystemDatabase::Sqlite(db) => db.remove_all::<T>().await,
188            #[cfg(target_arch = "wasm32")]
189            SystemDatabase::IndexedDb(db) => db.remove_all::<T>().await,
190            SystemDatabase::Memory(db) => db.remove_all::<T>().await,
191        }
192    }
193
194    async fn wipe(&self) -> Result<(), DatabaseError> {
195        match self {
196            #[cfg(not(target_arch = "wasm32"))]
197            SystemDatabase::Sqlite(db) => db.wipe().await,
198            #[cfg(target_arch = "wasm32")]
199            SystemDatabase::IndexedDb(db) => db.wipe().await,
200            SystemDatabase::Memory(db) => db.wipe().await,
201        }
202    }
203}
204
205struct DBRepository<T: RepositoryItem> {
206    database: SystemDatabase,
207    _marker: std::marker::PhantomData<T>,
208}
209
210#[async_trait::async_trait]
211impl<V: RepositoryItem> Repository<V> for DBRepository<V> {
212    async fn get(&self, key: V::Key) -> Result<Option<V>, RepositoryError> {
213        let key = key.to_string();
214        let value = self.database.get::<V>(&key).await?;
215        Ok(value)
216    }
217    async fn list(&self) -> Result<Vec<V>, RepositoryError> {
218        let values = self.database.list::<V>().await?;
219        Ok(values)
220    }
221    async fn set(&self, key: V::Key, value: V) -> Result<(), RepositoryError> {
222        let key = key.to_string();
223        Ok(self.database.set::<V>(&key, value).await?)
224    }
225    async fn set_bulk(&self, values: Vec<(V::Key, V)>) -> Result<(), RepositoryError> {
226        let values = values
227            .into_iter()
228            .map(|(k, v)| (k.to_string(), v))
229            .collect();
230        Ok(self.database.set_bulk::<V>(values).await?)
231    }
232    async fn remove(&self, key: V::Key) -> Result<(), RepositoryError> {
233        let key = key.to_string();
234        Ok(self.database.remove::<V>(&key).await?)
235    }
236    async fn remove_bulk(&self, keys: Vec<V::Key>) -> Result<(), RepositoryError> {
237        let keys = keys.into_iter().map(|k| k.to_string()).collect();
238        Ok(self.database.remove_bulk::<V>(keys).await?)
239    }
240    async fn remove_all(&self) -> Result<(), RepositoryError> {
241        Ok(self.database.remove_all::<V>().await?)
242    }
243}
244
245impl SystemDatabase {
246    pub(super) fn get_repository<V: RepositoryItem>(&self) -> Arc<dyn Repository<V>> {
247        Arc::new(DBRepository {
248            database: self.clone(),
249            _marker: std::marker::PhantomData,
250        })
251    }
252}