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}