Skip to main content

flowfull/
storage.rs

1use std::{collections::HashMap, path::PathBuf, sync::Arc};
2
3use async_trait::async_trait;
4use tokio::sync::RwLock;
5
6use crate::{FlowfullError, Result};
7
8#[async_trait]
9pub trait Storage: Send + Sync {
10    async fn get_item(&self, key: &str) -> Result<Option<String>>;
11    async fn set_item(&self, key: &str, value: &str) -> Result<()>;
12    async fn remove_item(&self, key: &str) -> Result<()>;
13    async fn clear(&self) -> Result<()>;
14}
15
16#[derive(Debug, Clone, Default)]
17pub struct MemoryStorage {
18    data: Arc<RwLock<HashMap<String, String>>>,
19}
20
21impl MemoryStorage {
22    pub fn new() -> Self {
23        Self::default()
24    }
25}
26
27#[async_trait]
28impl Storage for MemoryStorage {
29    async fn get_item(&self, key: &str) -> Result<Option<String>> {
30        Ok(self.data.read().await.get(key).cloned())
31    }
32
33    async fn set_item(&self, key: &str, value: &str) -> Result<()> {
34        self.data
35            .write()
36            .await
37            .insert(key.to_string(), value.to_string());
38        Ok(())
39    }
40
41    async fn remove_item(&self, key: &str) -> Result<()> {
42        self.data.write().await.remove(key);
43        Ok(())
44    }
45
46    async fn clear(&self) -> Result<()> {
47        self.data.write().await.clear();
48        Ok(())
49    }
50}
51
52#[derive(Debug, Clone)]
53pub struct FileStorage {
54    base_path: PathBuf,
55}
56
57impl FileStorage {
58    pub fn new(base_path: impl Into<PathBuf>) -> Self {
59        Self {
60            base_path: base_path.into(),
61        }
62    }
63
64    fn path_for_key(&self, key: &str) -> Result<PathBuf> {
65        if key.is_empty()
66            || key.contains('/')
67            || key.contains('\\')
68            || key.contains("..")
69            || key.contains(':')
70        {
71            return Err(FlowfullError::Storage(format!(
72                "invalid storage key: {key}"
73            )));
74        }
75        Ok(self.base_path.join(key))
76    }
77}
78
79#[async_trait]
80impl Storage for FileStorage {
81    async fn get_item(&self, key: &str) -> Result<Option<String>> {
82        let path = self.path_for_key(key)?;
83        match tokio::fs::read_to_string(path).await {
84            Ok(value) => Ok(Some(value)),
85            Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
86            Err(err) => Err(err.into()),
87        }
88    }
89
90    async fn set_item(&self, key: &str, value: &str) -> Result<()> {
91        tokio::fs::create_dir_all(&self.base_path).await?;
92        let path = self.path_for_key(key)?;
93        tokio::fs::write(path, value).await?;
94        Ok(())
95    }
96
97    async fn remove_item(&self, key: &str) -> Result<()> {
98        let path = self.path_for_key(key)?;
99        match tokio::fs::remove_file(path).await {
100            Ok(()) => Ok(()),
101            Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
102            Err(err) => Err(err.into()),
103        }
104    }
105
106    async fn clear(&self) -> Result<()> {
107        match tokio::fs::remove_dir_all(&self.base_path).await {
108            Ok(()) => Ok(()),
109            Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
110            Err(err) => Err(err.into()),
111        }
112    }
113}