1use crate::error::StorageError;
14use crate::storage::Storage;
15use async_trait::async_trait;
16use std::collections::HashMap;
17use std::sync::Arc;
18use tokio::sync::RwLock;
19
20pub struct UpYunStorage {
25 pub bucket: String,
26 store: Arc<RwLock<HashMap<String, Vec<u8>>>>,
27}
28
29impl UpYunStorage {
30 pub fn new(bucket: impl Into<String>) -> Self {
31 Self {
32 bucket: bucket.into(),
33 store: Arc::new(RwLock::new(HashMap::new())),
34 }
35 }
36
37 pub fn url_for(&self, key: &str) -> String {
38 format!("upyun://{}/{}", self.bucket, key)
39 }
40}
41
42#[async_trait]
43impl Storage for UpYunStorage {
44 async fn put(
45 &self,
46 key: &str,
47 data: &[u8],
48 _content_type: &str,
49 ) -> Result<String, StorageError> {
50 let mut store = self.store.write().await;
51 store.insert(key.to_string(), data.to_vec());
52 Ok(self.url_for(key))
53 }
54
55 async fn get(&self, key: &str) -> Result<Vec<u8>, StorageError> {
56 let store = self.store.read().await;
57 store
58 .get(key)
59 .cloned()
60 .ok_or_else(|| StorageError::NotFound(format!("upyun://{}/{}", self.bucket, key)))
61 }
62
63 async fn delete(&self, key: &str) -> Result<(), StorageError> {
64 let mut store = self.store.write().await;
65 store.remove(key);
66 Ok(())
67 }
68
69 async fn exists(&self, key: &str) -> Result<bool, StorageError> {
70 let store = self.store.read().await;
71 Ok(store.contains_key(key))
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[tokio::test]
80 async fn test_upyun_put_and_get() {
81 let storage = UpYunStorage::new("my-bucket");
82 let url = storage
83 .put("file.txt", b"upyun-data", "text/plain")
84 .await
85 .unwrap();
86 assert!(url.starts_with("upyun://my-bucket/"));
87 assert!(url.contains("file.txt"));
88
89 let data = storage.get("file.txt").await.unwrap();
90 assert_eq!(data, b"upyun-data");
91 }
92
93 #[tokio::test]
94 async fn test_upyun_get_not_found() {
95 let storage = UpYunStorage::new("bucket");
96 let result = storage.get("missing").await;
97 assert!(result.is_err());
98 assert!(matches!(result.unwrap_err(), StorageError::NotFound(_)));
99 }
100
101 #[tokio::test]
102 async fn test_upyun_delete_and_exists() {
103 let storage = UpYunStorage::new("bucket");
104 storage.put("key", b"data", "text/plain").await.unwrap();
105 assert!(storage.exists("key").await.unwrap());
106
107 storage.delete("key").await.unwrap();
108 assert!(!storage.exists("key").await.unwrap());
109 }
110
111 #[tokio::test]
112 async fn test_upyun_overwrite() {
113 let storage = UpYunStorage::new("bucket");
114 storage.put("key", b"v1", "text/plain").await.unwrap();
115 storage.put("key", b"v2", "text/plain").await.unwrap();
116 assert_eq!(storage.get("key").await.unwrap(), b"v2");
117 }
118
119 #[tokio::test]
120 async fn test_upyun_url_format() {
121 let storage = UpYunStorage::new("my-bucket");
122 assert_eq!(storage.url_for("file.txt"), "upyun://my-bucket/file.txt");
123 assert_eq!(
124 storage.url_for("dir/file.txt"),
125 "upyun://my-bucket/dir/file.txt"
126 );
127 }
128}