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