Skip to main content

sz_orm_storage/
qiniu.rs

1//! # Qiniu Kodo Storage(**MOCK-ONLY,非生产可用**)
2//!
3//! ⚠️ **重要警告:本模块为内存 Mock 实现,未集成真实七牛云 Kodo SDK。**
4//!
5//! - 所有数据存储在进程内 `HashMap`,重启即丢失
6//! - 不执行任何 HTTP 请求,不与真实 Kodo 服务交互
7//! - 不支持认证、签名、TLS、分片上传等任何 Kodo 生产特性
8//! - **请勿用于生产环境**——仅适用于单元测试与本地开发
9//!
10//! 如需真实 Kodo 集成,请基于 [`crate::storage::Storage`] trait
11//! 接入七牛云官方 SDK 实现。
12
13use 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
20/// 七牛云 Kodo 存储后端(**Mock 实现**)
21///
22/// ⚠️ 仅用于测试。所有数据存储在内存 `HashMap`,不与真实 Kodo 服务交互。
23pub 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}