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