Skip to main content

sz_orm_storage/
s3.rs

1//! # S3 Storage(**MOCK-ONLY,非生产可用**)
2//!
3//! ⚠️ **重要警告:本模块为内存 Mock 实现,未集成真实 AWS S3 SDK。**
4//!
5//! - 所有数据存储在进程内 `HashMap`,重启即丢失
6//! - 不执行任何 HTTP 请求,不与真实 S3 服务交互
7//! - 不支持认证、签名、TLS、分片上传等任何 S3 生产特性
8//! - **请勿用于生产环境**——仅适用于单元测试与本地开发
9//!
10//! 如需真实 S3 集成,请使用 [`crate::s3_sdk::S3SdkStorage`](基于 `aws-sdk-s3`),
11//! 或基于 [`crate::storage::Storage`] trait 自行实现官方 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/// AWS S3 存储后端(**Mock 实现**)
21///
22/// ⚠️ 仅用于测试。所有数据存储在内存 `HashMap`,不与真实 S3 服务交互。
23/// 如需生产使用,请使用 [`crate::s3_sdk::S3SdkStorage`]。
24pub struct S3Storage {
25    pub bucket: String,
26    pub region: String,
27    store: Arc<RwLock<HashMap<String, Vec<u8>>>>,
28}
29
30impl S3Storage {
31    pub fn new(bucket: impl Into<String>, region: impl Into<String>) -> Self {
32        Self {
33            bucket: bucket.into(),
34            region: region.into(),
35            store: Arc::new(RwLock::new(HashMap::new())),
36        }
37    }
38
39    pub fn url_for(&self, key: &str) -> String {
40        format!("s3://{}.{}/{}", self.bucket, self.region, key)
41    }
42}
43
44#[async_trait]
45impl Storage for S3Storage {
46    async fn put(
47        &self,
48        key: &str,
49        data: &[u8],
50        _content_type: &str,
51    ) -> Result<String, StorageError> {
52        let mut store = self.store.write().await;
53        store.insert(key.to_string(), data.to_vec());
54        Ok(self.url_for(key))
55    }
56
57    async fn get(&self, key: &str) -> Result<Vec<u8>, StorageError> {
58        let store = self.store.read().await;
59        store
60            .get(key)
61            .cloned()
62            .ok_or_else(|| StorageError::NotFound(format!("s3://{}/{}", self.bucket, key)))
63    }
64
65    async fn delete(&self, key: &str) -> Result<(), StorageError> {
66        let mut store = self.store.write().await;
67        store.remove(key);
68        Ok(())
69    }
70
71    async fn exists(&self, key: &str) -> Result<bool, StorageError> {
72        let store = self.store.read().await;
73        Ok(store.contains_key(key))
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[tokio::test]
82    async fn test_s3_put_and_get() {
83        let storage = S3Storage::new("my-bucket", "us-east-1");
84        let url = storage
85            .put("file.txt", b"hello s3", "text/plain")
86            .await
87            .unwrap();
88        assert!(url.starts_with("s3://my-bucket.us-east-1/"));
89        assert!(url.contains("file.txt"));
90
91        let data = storage.get("file.txt").await.unwrap();
92        assert_eq!(data, b"hello s3");
93    }
94
95    #[tokio::test]
96    async fn test_s3_get_not_found() {
97        let storage = S3Storage::new("bucket", "us-west-2");
98        let result = storage.get("missing.txt").await;
99        assert!(result.is_err());
100        assert!(matches!(result.unwrap_err(), StorageError::NotFound(_)));
101    }
102
103    #[tokio::test]
104    async fn test_s3_delete() {
105        let storage = S3Storage::new("bucket", "us-east-1");
106        storage.put("del.txt", b"data", "text/plain").await.unwrap();
107        assert!(storage.exists("del.txt").await.unwrap());
108
109        storage.delete("del.txt").await.unwrap();
110        assert!(!storage.exists("del.txt").await.unwrap());
111    }
112
113    #[tokio::test]
114    async fn test_s3_exists_false_for_missing() {
115        let storage = S3Storage::new("bucket", "us-east-1");
116        assert!(!storage.exists("nope").await.unwrap());
117    }
118
119    #[tokio::test]
120    async fn test_s3_overwrite() {
121        let storage = S3Storage::new("bucket", "us-east-1");
122        storage.put("key", b"v1", "text/plain").await.unwrap();
123        storage.put("key", b"v2", "text/plain").await.unwrap();
124        let data = storage.get("key").await.unwrap();
125        assert_eq!(data, b"v2");
126    }
127
128    #[tokio::test]
129    async fn test_s3_multiple_keys() {
130        let storage = S3Storage::new("bucket", "us-east-1");
131        storage.put("a", b"a-data", "text/plain").await.unwrap();
132        storage.put("b", b"b-data", "text/plain").await.unwrap();
133        storage.put("c", b"c-data", "text/plain").await.unwrap();
134
135        assert_eq!(storage.get("a").await.unwrap(), b"a-data");
136        assert_eq!(storage.get("b").await.unwrap(), b"b-data");
137        assert_eq!(storage.get("c").await.unwrap(), b"c-data");
138
139        storage.delete("b").await.unwrap();
140        assert!(storage.exists("a").await.unwrap());
141        assert!(!storage.exists("b").await.unwrap());
142        assert!(storage.exists("c").await.unwrap());
143    }
144}