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#[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}