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 HuaweiObsStorage {
24 pub bucket: String,
25 pub endpoint: String,
26 store: Arc<RwLock<HashMap<String, Vec<u8>>>>,
27}
28
29impl HuaweiObsStorage {
30 pub fn new(bucket: impl Into<String>, endpoint: impl Into<String>) -> Self {
31 Self {
32 bucket: bucket.into(),
33 endpoint: endpoint.into(),
34 store: Arc::new(RwLock::new(HashMap::new())),
35 }
36 }
37
38 pub fn url_for(&self, key: &str) -> String {
39 format!("obs://{}.{}/{}", self.bucket, self.endpoint, key)
40 }
41}
42
43#[async_trait]
44impl Storage for HuaweiObsStorage {
45 async fn put(
46 &self,
47 key: &str,
48 data: &[u8],
49 _content_type: &str,
50 ) -> Result<String, StorageError> {
51 let mut store = self.store.write().await;
52 store.insert(key.to_string(), data.to_vec());
53 Ok(self.url_for(key))
54 }
55
56 async fn get(&self, key: &str) -> Result<Vec<u8>, StorageError> {
57 let store = self.store.read().await;
58 store
59 .get(key)
60 .cloned()
61 .ok_or_else(|| StorageError::NotFound(format!("obs://{}/{}", self.bucket, key)))
62 }
63
64 async fn delete(&self, key: &str) -> Result<(), StorageError> {
65 let mut store = self.store.write().await;
66 store.remove(key);
67 Ok(())
68 }
69
70 async fn exists(&self, key: &str) -> Result<bool, StorageError> {
71 let store = self.store.read().await;
72 Ok(store.contains_key(key))
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[tokio::test]
81 async fn test_huawei_put_and_get() {
82 let storage = HuaweiObsStorage::new("my-bucket", "obs.cn-north-4.myhuaweicloud.com");
83 let url = storage
84 .put("a.txt", b"huawei-data", "text/plain")
85 .await
86 .unwrap();
87 assert!(url.starts_with("obs://my-bucket.obs.cn-north-4.myhuaweicloud.com/"));
88
89 let data = storage.get("a.txt").await.unwrap();
90 assert_eq!(data, b"huawei-data");
91 }
92
93 #[tokio::test]
94 async fn test_huawei_get_not_found() {
95 let storage = HuaweiObsStorage::new("bucket", "obs.cn-north-4.myhuaweicloud.com");
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_huawei_delete_and_exists() {
103 let storage = HuaweiObsStorage::new("bucket", "obs.cn-north-4.myhuaweicloud.com");
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_huawei_overwrite() {
113 let storage = HuaweiObsStorage::new("bucket", "obs.cn-north-4.myhuaweicloud.com");
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_huawei_multiple_keys() {
121 let storage = HuaweiObsStorage::new("bucket", "obs.cn-north-4.myhuaweicloud.com");
122 storage.put("a", b"a-data", "text/plain").await.unwrap();
123 storage.put("b", b"b-data", "text/plain").await.unwrap();
124
125 assert_eq!(storage.get("a").await.unwrap(), b"a-data");
126 assert_eq!(storage.get("b").await.unwrap(), b"b-data");
127
128 storage.delete("a").await.unwrap();
129 assert!(!storage.exists("a").await.unwrap());
130 assert!(storage.exists("b").await.unwrap());
131 }
132}