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