cache_vault/
lib.rs

1mod base32;
2mod connection;
3mod crypt;
4mod digest;
5mod error;
6mod key;
7mod models;
8mod vault_entry;
9
10#[allow(unused_imports)]
11use chrono::{DateTime, NaiveDateTime, Utc};
12use std::collections::HashMap;
13
14use crate::error::CacheVaultError;
15use crate::models::*;
16
17#[tracing::instrument]
18pub async fn init() -> Result<(), CacheVaultError> {
19    connection::migrate().await
20}
21
22#[tracing::instrument]
23pub async fn save(
24    namespace: &str,
25    key_name: &str,
26    value: &str,
27    attributes: Option<HashMap<String, String>>,
28    expired_at: Option<NaiveDateTime>,
29) -> Result<(), CacheVaultError> {
30    let entry_id = Entry::upsert(namespace, key_name, value, expired_at).await?;
31    if let Some(new_attributes) = attributes {
32        for (name, value) in new_attributes.iter() {
33            let _ = Attribute::upsert(entry_id, name, value).await?;
34        }
35    }
36    Ok(())
37}
38
39#[tracing::instrument]
40pub async fn fetch(namespace: &str, key_name: &str) -> Result<(String, Option<NaiveDateTime>), CacheVaultError> {
41    let entry = Entry::fetch(namespace, key_name).await?;
42    Ok((entry.plaintext()?, entry.expired_at))
43}
44
45#[tracing::instrument]
46pub async fn fetch_with_attributes(
47    namespace: &str,
48    key_name: &str,
49) -> Result<(String, Option<NaiveDateTime>, Option<HashMap<String, String>>), CacheVaultError> {
50    let entry = Entry::fetch(namespace, key_name).await?;
51    let attributes = Attribute::fetch_all(entry.id)
52        .await?
53        .iter()
54        .map(|a| Ok((a.name.to_string(), a.plaintext()?)))
55        .collect::<Result<HashMap<String, String>, CacheVaultError>>()?;
56    if attributes.is_empty() {
57        Ok((entry.plaintext()?, entry.expired_at, None))
58    } else {
59        Ok((entry.plaintext()?, entry.expired_at, Some(attributes)))
60    }
61}
62
63// fn async search_by_attributes(namespace: &str, attributes: HashMap<String, String>) -> Result<String, CacheVaultError> {
64//    todo!()
65// }
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::connection::migrate;
71
72    #[tracing_test::traced_test]
73    #[tokio::test]
74    async fn test_save() -> Result<(), CacheVaultError> {
75        migrate().await?;
76        save("test", "test-key1", "test-value1", None, None).await?;
77        save("test", "test-key2", "test-value2", None, None).await?;
78        let (value1, _) = fetch("test", "test-key1").await?;
79        assert_eq!(value1, "test-value1");
80        let (value2, _) = fetch("test", "test-key2").await?;
81        assert_eq!(value2, "test-value2");
82
83        let (value1, _, attributes) = fetch_with_attributes("test", "test-key1").await?;
84        assert_eq!(value1, "test-value1");
85        assert_eq!(attributes, None);
86
87        match fetch("test", "no-such-key").await {
88            Err(e) => match e {
89                CacheVaultError::SqlxError(sqlx::Error::RowNotFound) => (),
90                _ => panic!("unexpected"),
91            },
92            Ok(_) => panic!("unexpected"),
93        }
94
95        Ok(())
96    }
97
98    #[tracing_test::traced_test]
99    #[tokio::test]
100    async fn test_save_with_attributes() -> Result<(), CacheVaultError> {
101        migrate().await?;
102        let attributes = HashMap::from([
103            (String::from("attr1"), String::from("attr1-value")),
104            (String::from("attr2"), String::from("attr2-value")),
105            (String::from("attr3"), String::from("attr3-value")),
106        ]);
107        save("test", "test-key1", "test-value1", Some(attributes.clone()), None).await?;
108
109        if let (value1, _, Some(attrs)) = fetch_with_attributes("test", "test-key1").await? {
110            assert_eq!(value1, "test-value1");
111            assert_eq!(attrs, attributes);
112        } else {
113            panic!("unexpected");
114        }
115
116        Ok(())
117    }
118}