1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! Storage backends supported by aries-askar

use std::fmt::Debug;

use crate::{
    entry::{Entry, EntryKind, EntryOperation, EntryTag, Scan, TagFilter},
    error::{Error, ErrorKind},
    future::BoxFuture,
    protect::{PassKey, StoreKeyMethod},
};

#[cfg(any(feature = "postgres", feature = "sqlite"))]
pub(crate) mod db_utils;

#[cfg(feature = "postgres")]
#[cfg_attr(docsrs, doc(cfg(feature = "postgres")))]
/// Postgres database support
pub mod postgres;

#[cfg(feature = "sqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
/// Sqlite database support
pub mod sqlite;

/// Represents a generic backend implementation
pub trait Backend: Debug + Send + Sync {
    /// The type of session managed by this backend
    type Session: BackendSession + 'static;

    /// Create a new profile
    fn create_profile(&self, name: Option<String>) -> BoxFuture<'_, Result<String, Error>>;

    /// Get the name of the active profile
    fn get_active_profile(&self) -> String;

    /// Get the name of the default profile
    fn get_default_profile(&self) -> BoxFuture<'_, Result<String, Error>>;

    /// Set the the default profile
    fn set_default_profile(&self, profile: String) -> BoxFuture<'_, Result<(), Error>>;

    /// Get the details of all store profiles
    fn list_profiles(&self) -> BoxFuture<'_, Result<Vec<String>, Error>>;

    /// Remove an existing profile
    fn remove_profile(&self, name: String) -> BoxFuture<'_, Result<bool, Error>>;

    /// Create a [`Scan`] against the store
    fn scan(
        &self,
        profile: Option<String>,
        kind: Option<EntryKind>,
        category: Option<String>,
        tag_filter: Option<TagFilter>,
        offset: Option<i64>,
        limit: Option<i64>,
    ) -> BoxFuture<'_, Result<Scan<'static, Entry>, Error>>;

    /// Create a new session against the store
    fn session(&self, profile: Option<String>, transaction: bool) -> Result<Self::Session, Error>;

    /// Replace the wrapping key of the store
    fn rekey(
        &mut self,
        method: StoreKeyMethod,
        key: PassKey<'_>,
    ) -> BoxFuture<'_, Result<(), Error>>;

    /// Close the store instance
    fn close(&self) -> BoxFuture<'_, Result<(), Error>>;
}

/// Create, open, or remove a generic backend implementation
pub trait ManageBackend<'a> {
    /// The type of backend being managed
    type Backend: Backend;

    /// Open an existing store
    fn open_backend(
        self,
        method: Option<StoreKeyMethod>,
        pass_key: PassKey<'a>,
        profile: Option<String>,
    ) -> BoxFuture<'a, Result<Self::Backend, Error>>;

    /// Provision a new store
    fn provision_backend(
        self,
        method: StoreKeyMethod,
        pass_key: PassKey<'a>,
        profile: Option<String>,
        recreate: bool,
    ) -> BoxFuture<'a, Result<Self::Backend, Error>>;

    /// Remove an existing store
    fn remove_backend(self) -> BoxFuture<'a, Result<bool, Error>>;
}

/// Query from a generic backend implementation
pub trait BackendSession: Debug + Send {
    /// Count the number of matching records in the store
    fn count<'q>(
        &'q mut self,
        kind: Option<EntryKind>,
        category: Option<&'q str>,
        tag_filter: Option<TagFilter>,
    ) -> BoxFuture<'q, Result<i64, Error>>;

    /// Fetch a single record from the store by category and name
    fn fetch<'q>(
        &'q mut self,
        kind: EntryKind,
        category: &'q str,
        name: &'q str,
        for_update: bool,
    ) -> BoxFuture<'q, Result<Option<Entry>, Error>>;

    /// Fetch all matching records from the store
    fn fetch_all<'q>(
        &'q mut self,
        kind: Option<EntryKind>,
        category: Option<&'q str>,
        tag_filter: Option<TagFilter>,
        limit: Option<i64>,
        for_update: bool,
    ) -> BoxFuture<'q, Result<Vec<Entry>, Error>>;

    /// Insert scan results from another profile or store
    fn import_scan<'q>(
        &'q mut self,
        mut scan: Scan<'q, Entry>,
    ) -> BoxFuture<'_, Result<(), Error>> {
        Box::pin(async move {
            while let Some(rows) = scan.fetch_next().await? {
                for entry in rows {
                    self.update(
                        entry.kind,
                        EntryOperation::Insert,
                        entry.category.as_str(),
                        entry.name.as_str(),
                        Some(entry.value.as_ref()),
                        Some(entry.tags.as_ref()),
                        None,
                    )
                    .await?;
                }
            }
            Ok(())
        })
    }

    /// Remove all matching records from the store
    fn remove_all<'q>(
        &'q mut self,
        kind: Option<EntryKind>,
        category: Option<&'q str>,
        tag_filter: Option<TagFilter>,
    ) -> BoxFuture<'q, Result<i64, Error>>;

    /// Insert or replace a record in the store
    #[allow(clippy::too_many_arguments)]
    fn update<'q>(
        &'q mut self,
        kind: EntryKind,
        operation: EntryOperation,
        category: &'q str,
        name: &'q str,
        value: Option<&'q [u8]>,
        tags: Option<&'q [EntryTag]>,
        expiry_ms: Option<i64>,
    ) -> BoxFuture<'q, Result<(), Error>>;

    /// Test the connection to the store
    fn ping(&mut self) -> BoxFuture<'_, Result<(), Error>>;

    /// Close the current store session
    fn close(&mut self, commit: bool) -> BoxFuture<'_, Result<(), Error>>;
}

/// Insert all records from a given profile
pub async fn copy_profile<A: Backend, B: Backend>(
    from_backend: &A,
    to_backend: &B,
    from_profile: &str,
    to_profile: &str,
) -> Result<(), Error> {
    let scan = from_backend
        .scan(Some(from_profile.into()), None, None, None, None, None)
        .await?;
    if let Err(e) = to_backend.create_profile(Some(to_profile.into())).await {
        if e.kind() != ErrorKind::Duplicate {
            return Err(e);
        }
    }
    let mut txn = to_backend.session(Some(to_profile.into()), true)?;
    let count = txn.count(None, None, None).await?;
    if count > 0 {
        return Err(err_msg!(Input, "Profile targeted for import is not empty"));
    }
    txn.import_scan(scan).await?;
    txn.close(true).await?;
    Ok(())
}

/// Export an entire Store to another location
pub async fn copy_store<'m, B: Backend, M: ManageBackend<'m>>(
    source: &B,
    target: M,
    key_method: StoreKeyMethod,
    pass_key: PassKey<'m>,
    recreate: bool,
) -> Result<<M as ManageBackend<'m>>::Backend, Error> {
    let default_profile = source.get_default_profile().await?;
    let profile_ids = source.list_profiles().await?;
    let target = target
        .provision_backend(key_method, pass_key, Some(default_profile), recreate)
        .await?;
    for profile in profile_ids {
        copy_profile(source, &target, &profile, &profile).await?;
    }
    Ok(target)
}