Skip to main content

askar_storage/
any.rs

1//! Generic backend support
2
3use std::{fmt::Debug, sync::Arc};
4
5use super::{Backend, BackendSession, ManageBackend};
6use crate::{
7    backend::OrderBy,
8    entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter},
9    error::Error,
10    future::BoxFuture,
11    options::IntoOptions,
12    protect::{PassKey, StoreKeyMethod},
13};
14
15#[cfg(feature = "postgres")]
16use super::postgres;
17
18#[cfg(feature = "sqlite")]
19use super::sqlite;
20
21/// A dynamic store backend instance
22#[derive(Clone, Debug)]
23pub struct AnyBackend(Arc<dyn Backend<Session = AnyBackendSession>>);
24
25/// Wrap a backend instance into an AnyBackend
26pub fn into_any_backend(inst: impl Backend + 'static) -> AnyBackend {
27    AnyBackend(Arc::new(WrapBackend(inst)))
28}
29
30/// This structure turns a generic backend into a concrete type
31#[derive(Debug)]
32struct WrapBackend<B: Backend>(B);
33
34impl<B: Backend> Backend for WrapBackend<B> {
35    type Session = AnyBackendSession;
36
37    #[inline]
38    fn create_profile(&self, name: Option<String>) -> BoxFuture<'_, Result<String, Error>> {
39        self.0.create_profile(name)
40    }
41
42    #[inline]
43    fn get_active_profile(&self) -> String {
44        self.0.get_active_profile()
45    }
46
47    #[inline]
48    fn get_default_profile(&self) -> BoxFuture<'_, Result<String, Error>> {
49        self.0.get_default_profile()
50    }
51
52    #[inline]
53    fn set_default_profile(&self, profile: String) -> BoxFuture<'_, Result<(), Error>> {
54        self.0.set_default_profile(profile)
55    }
56
57    #[inline]
58    fn list_profiles(&self) -> BoxFuture<'_, Result<Vec<String>, Error>> {
59        self.0.list_profiles()
60    }
61
62    #[inline]
63    fn remove_profile(&self, name: String) -> BoxFuture<'_, Result<bool, Error>> {
64        self.0.remove_profile(name)
65    }
66
67    #[inline]
68    fn rename_profile(
69        &self,
70        from_name: String,
71        to_name: String,
72    ) -> BoxFuture<'_, Result<bool, Error>> {
73        self.0.rename_profile(from_name, to_name)
74    }
75
76    #[inline]
77    fn scan(
78        &self,
79        profile: Option<String>,
80        kind: Option<EntryKind>,
81        category: Option<String>,
82        tag_filter: Option<TagFilter>,
83        offset: Option<i64>,
84        limit: Option<i64>,
85        order_by: Option<OrderBy>,
86        descending: bool,
87    ) -> BoxFuture<'_, Result<Scan<'static, Entry>, Error>> {
88        self.0.scan(
89            profile, kind, category, tag_filter, offset, limit, order_by, descending,
90        )
91    }
92
93    #[inline]
94    fn session(&self, profile: Option<String>, transaction: bool) -> Result<Self::Session, Error> {
95        Ok(AnyBackendSession(Box::new(
96            self.0.session(profile, transaction)?,
97        )))
98    }
99
100    #[inline]
101    fn rekey(
102        &mut self,
103        method: StoreKeyMethod,
104        key: PassKey<'_>,
105    ) -> BoxFuture<'_, Result<(), Error>> {
106        self.0.rekey(method, key)
107    }
108
109    #[inline]
110    fn close(&self) -> BoxFuture<'_, Result<(), Error>> {
111        self.0.close()
112    }
113}
114
115// Forward to the concrete inner backend instance
116impl Backend for AnyBackend {
117    type Session = AnyBackendSession;
118
119    #[inline]
120    fn create_profile(&self, name: Option<String>) -> BoxFuture<'_, Result<String, Error>> {
121        self.0.create_profile(name)
122    }
123
124    #[inline]
125    fn get_active_profile(&self) -> String {
126        self.0.get_active_profile()
127    }
128
129    #[inline]
130    fn get_default_profile(&self) -> BoxFuture<'_, Result<String, Error>> {
131        self.0.get_default_profile()
132    }
133
134    #[inline]
135    fn set_default_profile(&self, profile: String) -> BoxFuture<'_, Result<(), Error>> {
136        self.0.set_default_profile(profile)
137    }
138
139    #[inline]
140    fn list_profiles(&self) -> BoxFuture<'_, Result<Vec<String>, Error>> {
141        self.0.list_profiles()
142    }
143
144    #[inline]
145    fn remove_profile(&self, name: String) -> BoxFuture<'_, Result<bool, Error>> {
146        self.0.remove_profile(name)
147    }
148
149    #[inline]
150    fn rename_profile(
151        &self,
152        from_name: String,
153        to_name: String,
154    ) -> BoxFuture<'_, Result<bool, Error>> {
155        self.0.rename_profile(from_name, to_name)
156    }
157
158    #[inline]
159    fn scan(
160        &self,
161        profile: Option<String>,
162        kind: Option<EntryKind>,
163        category: Option<String>,
164        tag_filter: Option<TagFilter>,
165        offset: Option<i64>,
166        limit: Option<i64>,
167        order_by: Option<OrderBy>,
168        descending: bool,
169    ) -> BoxFuture<'_, Result<Scan<'static, Entry>, Error>> {
170        self.0.scan(
171            profile, kind, category, tag_filter, offset, limit, order_by, descending,
172        )
173    }
174
175    #[inline]
176    fn session(&self, profile: Option<String>, transaction: bool) -> Result<Self::Session, Error> {
177        Ok(AnyBackendSession(Box::new(
178            self.0.session(profile, transaction)?,
179        )))
180    }
181
182    #[inline]
183    fn rekey(
184        &mut self,
185        method: StoreKeyMethod,
186        key: PassKey<'_>,
187    ) -> BoxFuture<'_, Result<(), Error>> {
188        match Arc::get_mut(&mut self.0) {
189            Some(inner) => inner.rekey(method, key),
190            None => Box::pin(std::future::ready(Err(err_msg!(
191                "Cannot re-key a store with multiple references"
192            )))),
193        }
194    }
195
196    #[inline]
197    fn close(&self) -> BoxFuture<'_, Result<(), Error>> {
198        self.0.close()
199    }
200}
201
202/// A dynamic store session instance
203#[derive(Debug)]
204pub struct AnyBackendSession(Box<dyn BackendSession>);
205
206impl BackendSession for AnyBackendSession {
207    /// Count the number of matching records in the store
208    fn count<'q>(
209        &'q mut self,
210        kind: Option<EntryKind>,
211        category: Option<&'q str>,
212        tag_filter: Option<TagFilter>,
213    ) -> BoxFuture<'q, Result<i64, Error>> {
214        self.0.count(kind, category, tag_filter)
215    }
216
217    /// Fetch a single record from the store by category and name
218    fn fetch<'q>(
219        &'q mut self,
220        kind: EntryKind,
221        category: &'q str,
222        name: &'q str,
223        for_update: bool,
224    ) -> BoxFuture<'q, Result<Option<Entry>, Error>> {
225        self.0.fetch(kind, category, name, for_update)
226    }
227
228    /// Fetch all matching records from the store
229    fn fetch_all<'q>(
230        &'q mut self,
231        kind: Option<EntryKind>,
232        category: Option<&'q str>,
233        tag_filter: Option<TagFilter>,
234        limit: Option<i64>,
235        order_by: Option<OrderBy>,
236        descending: bool,
237        for_update: bool,
238    ) -> BoxFuture<'q, Result<Vec<Entry>, Error>> {
239        self.0.fetch_all(
240            kind, category, tag_filter, limit, order_by, descending, for_update,
241        )
242    }
243
244    /// Remove all matching records from the store
245    fn remove_all<'q>(
246        &'q mut self,
247        kind: Option<EntryKind>,
248        category: Option<&'q str>,
249        tag_filter: Option<TagFilter>,
250    ) -> BoxFuture<'q, Result<i64, Error>> {
251        self.0.remove_all(kind, category, tag_filter)
252    }
253
254    /// Insert or replace a record in the store
255    #[allow(clippy::too_many_arguments)]
256    fn update<'q>(
257        &'q mut self,
258        kind: EntryKind,
259        operation: EntryOperation,
260        category: &'q str,
261        name: &'q str,
262        value: Option<&'q [u8]>,
263        tags: Option<&'q [EntryTag]>,
264        expiry_ms: Option<i64>,
265    ) -> BoxFuture<'q, Result<(), Error>> {
266        self.0
267            .update(kind, operation, category, name, value, tags, expiry_ms)
268    }
269
270    /// Test the connection to the store
271    fn ping(&mut self) -> BoxFuture<'_, Result<(), Error>> {
272        self.0.ping()
273    }
274
275    /// Close the current store session
276    fn close(&mut self, commit: bool) -> BoxFuture<'_, Result<(), Error>> {
277        self.0.close(commit)
278    }
279}
280
281impl<'a> ManageBackend<'a> for &'a str {
282    type Backend = AnyBackend;
283
284    fn open_backend(
285        self,
286        method: Option<StoreKeyMethod>,
287        pass_key: PassKey<'a>,
288        profile: Option<String>,
289    ) -> BoxFuture<'a, Result<Self::Backend, Error>> {
290        Box::pin(async move {
291            let opts = self.into_options()?;
292            debug!("Open store with options: {:?}", &opts);
293
294            match opts.scheme.as_ref() {
295                #[cfg(feature = "postgres")]
296                "postgres" => {
297                    let opts = postgres::PostgresStoreOptions::new(opts)?;
298                    let mgr = opts.open(method, pass_key, profile).await?;
299                    Ok(into_any_backend(mgr))
300                }
301
302                #[cfg(feature = "sqlite")]
303                "sqlite" => {
304                    let opts = sqlite::SqliteStoreOptions::new(opts)?;
305                    let mgr = opts.open(method, pass_key, profile).await?;
306                    Ok(into_any_backend(mgr))
307                }
308
309                _ => Err(err_msg!(
310                    Unsupported,
311                    "Unsupported backend: {}",
312                    &opts.scheme
313                )),
314            }
315        })
316    }
317
318    fn provision_backend(
319        self,
320        method: StoreKeyMethod,
321        pass_key: PassKey<'a>,
322        profile: Option<String>,
323        recreate: bool,
324    ) -> BoxFuture<'a, Result<Self::Backend, Error>> {
325        Box::pin(async move {
326            let opts = self.into_options()?;
327            debug!("Provision store with options: {:?}", &opts);
328
329            match opts.scheme.as_ref() {
330                #[cfg(feature = "postgres")]
331                "postgres" => {
332                    let opts = postgres::PostgresStoreOptions::new(opts)?;
333                    let mgr = opts.provision(method, pass_key, profile, recreate).await?;
334                    Ok(into_any_backend(mgr))
335                }
336
337                #[cfg(feature = "sqlite")]
338                "sqlite" => {
339                    let opts = sqlite::SqliteStoreOptions::new(opts)?;
340                    let mgr = opts.provision(method, pass_key, profile, recreate).await?;
341                    Ok(into_any_backend(mgr))
342                }
343
344                _ => Err(err_msg!(
345                    Unsupported,
346                    "Unsupported backend: {}",
347                    &opts.scheme
348                )),
349            }
350        })
351    }
352
353    fn remove_backend(self) -> BoxFuture<'a, Result<bool, Error>> {
354        Box::pin(async move {
355            let opts = self.into_options()?;
356            debug!("Remove store with options: {:?}", &opts);
357
358            match opts.scheme.as_ref() {
359                #[cfg(feature = "postgres")]
360                "postgres" => {
361                    let opts = postgres::PostgresStoreOptions::new(opts)?;
362                    Ok(opts.remove().await?)
363                }
364
365                #[cfg(feature = "sqlite")]
366                "sqlite" => {
367                    let opts = sqlite::SqliteStoreOptions::new(opts)?;
368                    Ok(opts.remove().await?)
369                }
370
371                _ => Err(err_msg!(
372                    Unsupported,
373                    "Unsupported backend: {}",
374                    &opts.scheme
375                )),
376            }
377        })
378    }
379}