askar_storage/backend/
mod.rs

1//! Storage backends supported by aries-askar
2
3use std::fmt::Debug;
4
5use crate::{
6    entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter},
7    error::{Error, ErrorKind},
8    future::BoxFuture,
9    protect::{PassKey, StoreKeyMethod},
10};
11
12#[cfg(any(feature = "postgres", feature = "sqlite"))]
13pub(crate) mod db_utils;
14
15#[cfg(feature = "postgres")]
16#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))]
17/// Postgres database support
18pub mod postgres;
19
20#[cfg(feature = "sqlite")]
21#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
22/// Sqlite database support
23pub mod sqlite;
24
25/// Enum to support custom ordering in record queries
26#[derive(Debug, Default)]
27pub enum OrderBy {
28    /// Order by ID field
29    #[default]
30    Id,
31}
32
33/// Represents a generic backend implementation
34pub trait Backend: Debug + Send + Sync {
35    /// The type of session managed by this backend
36    type Session: BackendSession + 'static;
37
38    /// Create a new profile
39    fn create_profile(&self, name: Option<String>) -> BoxFuture<'_, Result<String, Error>>;
40
41    /// Get the name of the active profile
42    fn get_active_profile(&self) -> String;
43
44    /// Get the name of the default profile
45    fn get_default_profile(&self) -> BoxFuture<'_, Result<String, Error>>;
46
47    /// Set the the default profile
48    fn set_default_profile(&self, profile: String) -> BoxFuture<'_, Result<(), Error>>;
49
50    /// Get the details of all store profiles
51    fn list_profiles(&self) -> BoxFuture<'_, Result<Vec<String>, Error>>;
52
53    /// Remove an existing profile
54    fn remove_profile(&self, name: String) -> BoxFuture<'_, Result<bool, Error>>;
55
56    /// Create a [`Scan`] against the store
57    #[allow(clippy::too_many_arguments)]
58    fn scan(
59        &self,
60        profile: Option<String>,
61        kind: Option<EntryKind>,
62        category: Option<String>,
63        tag_filter: Option<TagFilter>,
64        offset: Option<i64>,
65        limit: Option<i64>,
66        order_by: Option<OrderBy>,
67        descending: bool,
68    ) -> BoxFuture<'_, Result<Scan<'static, Entry>, Error>>;
69
70    /// Create a new session against the store
71    fn session(&self, profile: Option<String>, transaction: bool) -> Result<Self::Session, Error>;
72
73    /// Replace the wrapping key of the store
74    fn rekey(
75        &mut self,
76        method: StoreKeyMethod,
77        key: PassKey<'_>,
78    ) -> BoxFuture<'_, Result<(), Error>>;
79
80    /// Close the store instance
81    fn close(&self) -> BoxFuture<'_, Result<(), Error>>;
82}
83
84/// Create, open, or remove a generic backend implementation
85pub trait ManageBackend<'a> {
86    /// The type of backend being managed
87    type Backend: Backend;
88
89    /// Open an existing store
90    fn open_backend(
91        self,
92        method: Option<StoreKeyMethod>,
93        pass_key: PassKey<'a>,
94        profile: Option<String>,
95    ) -> BoxFuture<'a, Result<Self::Backend, Error>>;
96
97    /// Provision a new store
98    fn provision_backend(
99        self,
100        method: StoreKeyMethod,
101        pass_key: PassKey<'a>,
102        profile: Option<String>,
103        recreate: bool,
104    ) -> BoxFuture<'a, Result<Self::Backend, Error>>;
105
106    /// Remove an existing store
107    fn remove_backend(self) -> BoxFuture<'a, Result<bool, Error>>;
108}
109
110/// Query from a generic backend implementation
111pub trait BackendSession: Debug + Send {
112    /// Count the number of matching records in the store
113    fn count<'q>(
114        &'q mut self,
115        kind: Option<EntryKind>,
116        category: Option<&'q str>,
117        tag_filter: Option<TagFilter>,
118    ) -> BoxFuture<'q, Result<i64, Error>>;
119
120    /// Fetch a single record from the store by category and name
121    fn fetch<'q>(
122        &'q mut self,
123        kind: EntryKind,
124        category: &'q str,
125        name: &'q str,
126        for_update: bool,
127    ) -> BoxFuture<'q, Result<Option<Entry>, Error>>;
128
129    /// Fetch all matching records from the store
130    #[allow(clippy::too_many_arguments)]
131    fn fetch_all<'q>(
132        &'q mut self,
133        kind: Option<EntryKind>,
134        category: Option<&'q str>,
135        tag_filter: Option<TagFilter>,
136        limit: Option<i64>,
137        order_by: Option<OrderBy>,
138        descending: bool,
139        for_update: bool,
140    ) -> BoxFuture<'q, Result<Vec<Entry>, Error>>;
141
142    /// Insert scan results from another profile or store
143    fn import_scan<'q>(
144        &'q mut self,
145        mut scan: Scan<'q, Entry>,
146    ) -> BoxFuture<'_, Result<(), Error>> {
147        Box::pin(async move {
148            while let Some(rows) = scan.fetch_next().await? {
149                for entry in rows {
150                    self.update(
151                        entry.kind,
152                        EntryOperation::Insert,
153                        entry.category.as_str(),
154                        entry.name.as_str(),
155                        Some(entry.value.as_ref()),
156                        Some(entry.tags.as_ref()),
157                        None,
158                    )
159                    .await?;
160                }
161            }
162            Ok(())
163        })
164    }
165
166    /// Remove all matching records from the store
167    fn remove_all<'q>(
168        &'q mut self,
169        kind: Option<EntryKind>,
170        category: Option<&'q str>,
171        tag_filter: Option<TagFilter>,
172    ) -> BoxFuture<'q, Result<i64, Error>>;
173
174    /// Insert or replace a record in the store
175    #[allow(clippy::too_many_arguments)]
176    fn update<'q>(
177        &'q mut self,
178        kind: EntryKind,
179        operation: EntryOperation,
180        category: &'q str,
181        name: &'q str,
182        value: Option<&'q [u8]>,
183        tags: Option<&'q [EntryTag]>,
184        expiry_ms: Option<i64>,
185    ) -> BoxFuture<'q, Result<(), Error>>;
186
187    /// Test the connection to the store
188    fn ping(&mut self) -> BoxFuture<'_, Result<(), Error>>;
189
190    /// Close the current store session
191    fn close(&mut self, commit: bool) -> BoxFuture<'_, Result<(), Error>>;
192}
193
194/// Insert all records from a given profile
195pub async fn copy_profile<A: Backend, B: Backend>(
196    from_backend: &A,
197    to_backend: &B,
198    from_profile: &str,
199    to_profile: &str,
200) -> Result<(), Error> {
201    let scan = from_backend
202        .scan(
203            Some(from_profile.into()),
204            None,
205            None,
206            None,
207            None,
208            None,
209            None,
210            false,
211        )
212        .await?;
213    if let Err(e) = to_backend.create_profile(Some(to_profile.into())).await {
214        if e.kind() != ErrorKind::Duplicate {
215            return Err(e);
216        }
217    }
218    let mut txn = to_backend.session(Some(to_profile.into()), true)?;
219    let count = txn.count(None, None, None).await?;
220    if count > 0 {
221        return Err(err_msg!(Input, "Profile targeted for import is not empty"));
222    }
223    txn.import_scan(scan).await?;
224    txn.close(true).await?;
225    Ok(())
226}
227
228/// Export an entire Store to another location
229pub async fn copy_store<'m, B: Backend, M: ManageBackend<'m>>(
230    source: &B,
231    target: M,
232    key_method: StoreKeyMethod,
233    pass_key: PassKey<'m>,
234    recreate: bool,
235) -> Result<<M as ManageBackend<'m>>::Backend, Error> {
236    let default_profile = source.get_default_profile().await?;
237    let profile_ids = source.list_profiles().await?;
238    let target = target
239        .provision_backend(key_method, pass_key, Some(default_profile), recreate)
240        .await?;
241    for profile in profile_ids {
242        copy_profile(source, &target, &profile, &profile).await?;
243    }
244    Ok(target)
245}