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")))]
pub mod postgres;
#[cfg(feature = "sqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
pub mod sqlite;
pub trait Backend: Debug + Send + Sync {
type Session: BackendSession + 'static;
fn create_profile(&self, name: Option<String>) -> BoxFuture<'_, Result<String, Error>>;
fn get_active_profile(&self) -> String;
fn get_default_profile(&self) -> BoxFuture<'_, Result<String, Error>>;
fn set_default_profile(&self, profile: String) -> BoxFuture<'_, Result<(), Error>>;
fn list_profiles(&self) -> BoxFuture<'_, Result<Vec<String>, Error>>;
fn remove_profile(&self, name: String) -> BoxFuture<'_, Result<bool, Error>>;
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>>;
fn session(&self, profile: Option<String>, transaction: bool) -> Result<Self::Session, Error>;
fn rekey(
&mut self,
method: StoreKeyMethod,
key: PassKey<'_>,
) -> BoxFuture<'_, Result<(), Error>>;
fn close(&self) -> BoxFuture<'_, Result<(), Error>>;
}
pub trait ManageBackend<'a> {
type Backend: Backend;
fn open_backend(
self,
method: Option<StoreKeyMethod>,
pass_key: PassKey<'a>,
profile: Option<String>,
) -> BoxFuture<'a, Result<Self::Backend, Error>>;
fn provision_backend(
self,
method: StoreKeyMethod,
pass_key: PassKey<'a>,
profile: Option<String>,
recreate: bool,
) -> BoxFuture<'a, Result<Self::Backend, Error>>;
fn remove_backend(self) -> BoxFuture<'a, Result<bool, Error>>;
}
pub trait BackendSession: Debug + Send {
fn count<'q>(
&'q mut self,
kind: Option<EntryKind>,
category: Option<&'q str>,
tag_filter: Option<TagFilter>,
) -> BoxFuture<'q, Result<i64, Error>>;
fn fetch<'q>(
&'q mut self,
kind: EntryKind,
category: &'q str,
name: &'q str,
for_update: bool,
) -> BoxFuture<'q, Result<Option<Entry>, Error>>;
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>>;
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(())
})
}
fn remove_all<'q>(
&'q mut self,
kind: Option<EntryKind>,
category: Option<&'q str>,
tag_filter: Option<TagFilter>,
) -> BoxFuture<'q, Result<i64, Error>>;
#[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>>;
fn ping(&mut self) -> BoxFuture<'_, Result<(), Error>>;
fn close(&mut self, commit: bool) -> BoxFuture<'_, Result<(), Error>>;
}
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(())
}
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)
}