cos_rust_sdk/
bucket.rs

1//! 存储桶管理模块
2//!
3//! 提供存储桶的创建、删除、列表等管理功能
4
5use crate::client::CosClient;
6use crate::error::{CosError, Result};
7use serde::Deserialize;
8use std::collections::HashMap;
9
10/// 存储桶操作客户端
11#[derive(Debug, Clone)]
12pub struct BucketClient {
13    client: CosClient,
14}
15
16impl BucketClient {
17    /// 创建新的存储桶操作客户端
18    pub fn new(client: CosClient) -> Self {
19        Self { client }
20    }
21
22    /// 创建存储桶
23    pub async fn create_bucket(&self, acl: Option<BucketAcl>) -> Result<()> {
24        let params = HashMap::new();
25        let mut headers = HashMap::new();
26        
27        if let Some(acl) = acl {
28            headers.insert("x-cos-acl".to_string(), acl.to_string());
29        }
30        
31        let _response = self.client.put("/", params, None::<&[u8]>).await?;
32        Ok(())
33    }
34
35    /// 删除存储桶
36    pub async fn delete_bucket(&self) -> Result<()> {
37        let params = HashMap::new();
38        let _response = self.client.delete("/", params).await?;
39        Ok(())
40    }
41
42    /// 检查存储桶是否存在
43    pub async fn bucket_exists(&self) -> Result<bool> {
44        let params = HashMap::new();
45        match self.client.head("/", params).await {
46            Ok(_) => Ok(true),
47            Err(CosError::Server { .. }) => Ok(false),
48            Err(e) => Err(e),
49        }
50    }
51
52    /// 获取存储桶位置
53    pub async fn get_bucket_location(&self) -> Result<String> {
54        let mut params = HashMap::new();
55        params.insert("location".to_string(), "".to_string());
56        
57        let response = self.client.get("/", params).await?;
58        let response_text = response
59            .text()
60            .await
61            .map_err(|e| CosError::other(format!("Failed to read response: {}", e)))?;
62        
63        let location_response: LocationResponse = quick_xml::de::from_str(&response_text)
64            .map_err(|e| CosError::other(format!("Failed to parse location response: {}", e)))?;
65        
66        Ok(location_response.location_constraint)
67    }
68
69    /// 列出存储桶中的对象
70    pub async fn list_objects(
71        &self,
72        options: Option<ListObjectsOptions>,
73    ) -> Result<ListObjectsResponse> {
74        let mut params = HashMap::new();
75        
76        if let Some(opts) = options {
77            if let Some(prefix) = opts.prefix {
78                params.insert("prefix".to_string(), prefix);
79            }
80            if let Some(delimiter) = opts.delimiter {
81                params.insert("delimiter".to_string(), delimiter);
82            }
83            if let Some(marker) = opts.marker {
84                params.insert("marker".to_string(), marker);
85            }
86            if let Some(max_keys) = opts.max_keys {
87                params.insert("max-keys".to_string(), max_keys.to_string());
88            }
89        }
90        
91        let response = self.client.get("/", params).await?;
92        let response_text = response
93            .text()
94            .await
95            .map_err(|e| CosError::other(format!("Failed to read response: {}", e)))?;
96        
97        let list_response: ListObjectsResponse = quick_xml::de::from_str(&response_text)
98            .map_err(|e| CosError::other(format!("Failed to parse list objects response: {}", e)))?;
99        
100        Ok(list_response)
101    }
102
103    /// 列出存储桶中的对象(V2版本)
104    pub async fn list_objects_v2(
105        &self,
106        options: Option<ListObjectsV2Options>,
107    ) -> Result<ListObjectsV2Response> {
108        let mut params = HashMap::new();
109        params.insert("list-type".to_string(), "2".to_string());
110        
111        if let Some(opts) = options {
112            if let Some(prefix) = opts.prefix {
113                params.insert("prefix".to_string(), prefix);
114            }
115            if let Some(delimiter) = opts.delimiter {
116                params.insert("delimiter".to_string(), delimiter);
117            }
118            if let Some(continuation_token) = opts.continuation_token {
119                params.insert("continuation-token".to_string(), continuation_token);
120            }
121            if let Some(max_keys) = opts.max_keys {
122                params.insert("max-keys".to_string(), max_keys.to_string());
123            }
124            if let Some(start_after) = opts.start_after {
125                params.insert("start-after".to_string(), start_after);
126            }
127        }
128        
129        let response = self.client.get("/", params).await?;
130        let response_text = response
131            .text()
132            .await
133            .map_err(|e| CosError::other(format!("Failed to read response: {}", e)))?;
134        
135        let list_response: ListObjectsV2Response = quick_xml::de::from_str(&response_text)
136            .map_err(|e| CosError::other(format!("Failed to parse list objects v2 response: {}", e)))?;
137        
138        Ok(list_response)
139    }
140
141    /// 获取存储桶ACL
142    pub async fn get_bucket_acl(&self) -> Result<BucketAclResponse> {
143        let mut params = HashMap::new();
144        params.insert("acl".to_string(), "".to_string());
145        
146        let response = self.client.get("/", params).await?;
147        let response_text = response
148            .text()
149            .await
150            .map_err(|e| CosError::other(format!("Failed to read response: {}", e)))?;
151        
152        let acl_response: BucketAclResponse = quick_xml::de::from_str(&response_text)
153            .map_err(|e| CosError::other(format!("Failed to parse ACL response: {}", e)))?;
154        
155        Ok(acl_response)
156    }
157
158    /// 设置存储桶ACL
159    pub async fn put_bucket_acl(&self, acl: BucketAcl) -> Result<()> {
160        let mut params = HashMap::new();
161        params.insert("acl".to_string(), "".to_string());
162        
163        let mut headers = HashMap::new();
164        headers.insert("x-cos-acl".to_string(), acl.to_string());
165        
166        let _response = self.client.put("/", params, None::<&[u8]>).await?;
167        Ok(())
168    }
169
170    /// 获取存储桶版本控制状态
171    pub async fn get_bucket_versioning(&self) -> Result<VersioningResponse> {
172        let mut params = HashMap::new();
173        params.insert("versioning".to_string(), "".to_string());
174        
175        let response = self.client.get("/", params).await?;
176        let response_text = response
177            .text()
178            .await
179            .map_err(|e| CosError::other(format!("Failed to read response: {}", e)))?;
180        
181        let versioning_response: VersioningResponse = quick_xml::de::from_str(&response_text)
182            .map_err(|e| CosError::other(format!("Failed to parse versioning response: {}", e)))?;
183        
184        Ok(versioning_response)
185    }
186}
187
188/// 存储桶ACL类型
189#[derive(Debug, Clone, Copy)]
190pub enum BucketAcl {
191    Private,
192    PublicRead,
193    PublicReadWrite,
194    AuthenticatedRead,
195}
196
197impl ToString for BucketAcl {
198    fn to_string(&self) -> String {
199        match self {
200            BucketAcl::Private => "private".to_string(),
201            BucketAcl::PublicRead => "public-read".to_string(),
202            BucketAcl::PublicReadWrite => "public-read-write".to_string(),
203            BucketAcl::AuthenticatedRead => "authenticated-read".to_string(),
204        }
205    }
206}
207
208/// 列出对象选项
209#[derive(Debug, Clone, Default)]
210pub struct ListObjectsOptions {
211    pub prefix: Option<String>,
212    pub delimiter: Option<String>,
213    pub marker: Option<String>,
214    pub max_keys: Option<u32>,
215}
216
217/// 列出对象V2选项
218#[derive(Debug, Clone, Default)]
219pub struct ListObjectsV2Options {
220    pub prefix: Option<String>,
221    pub delimiter: Option<String>,
222    pub continuation_token: Option<String>,
223    pub max_keys: Option<u32>,
224    pub start_after: Option<String>,
225}
226
227/// 存储桶位置响应
228#[derive(Debug, Deserialize)]
229#[serde(rename = "LocationConstraint")]
230struct LocationResponse {
231    #[serde(rename = "$text", default)]
232    location_constraint: String,
233}
234
235/// 列出对象响应
236#[derive(Debug, Deserialize)]
237#[serde(rename = "ListBucketResult")]
238pub struct ListObjectsResponse {
239    #[serde(rename = "Name")]
240    pub name: String,
241    #[serde(rename = "Prefix", default)]
242    pub prefix: String,
243    #[serde(rename = "Marker", default)]
244    pub marker: String,
245    #[serde(rename = "MaxKeys")]
246    pub max_keys: u32,
247    #[serde(rename = "IsTruncated")]
248    pub is_truncated: bool,
249    #[serde(rename = "Contents", default)]
250    pub contents: Vec<ObjectInfo>,
251    #[serde(rename = "CommonPrefixes", default)]
252    pub common_prefixes: Vec<CommonPrefix>,
253}
254
255/// 列出对象V2响应
256#[derive(Debug, Deserialize)]
257#[serde(rename = "ListBucketResult")]
258pub struct ListObjectsV2Response {
259    #[serde(rename = "Name")]
260    pub name: String,
261    #[serde(rename = "Prefix", default)]
262    pub prefix: String,
263    #[serde(rename = "KeyCount")]
264    pub key_count: u32,
265    #[serde(rename = "MaxKeys")]
266    pub max_keys: u32,
267    #[serde(rename = "IsTruncated")]
268    pub is_truncated: bool,
269    #[serde(rename = "ContinuationToken", default)]
270    pub continuation_token: String,
271    #[serde(rename = "NextContinuationToken", default)]
272    pub next_continuation_token: String,
273    #[serde(rename = "Contents", default)]
274    pub contents: Vec<ObjectInfo>,
275    #[serde(rename = "CommonPrefixes", default)]
276    pub common_prefixes: Vec<CommonPrefix>,
277}
278
279/// 对象信息
280#[derive(Debug, Deserialize)]
281pub struct ObjectInfo {
282    #[serde(rename = "Key")]
283    pub key: String,
284    #[serde(rename = "LastModified")]
285    pub last_modified: String,
286    #[serde(rename = "ETag")]
287    pub etag: String,
288    #[serde(rename = "Size")]
289    pub size: u64,
290    #[serde(rename = "StorageClass", default)]
291    pub storage_class: String,
292}
293
294/// 公共前缀
295#[derive(Debug, Deserialize)]
296pub struct CommonPrefix {
297    #[serde(rename = "Prefix")]
298    pub prefix: String,
299}
300
301/// 存储桶ACL响应
302#[derive(Debug, Deserialize)]
303#[serde(rename = "AccessControlPolicy")]
304pub struct BucketAclResponse {
305    #[serde(rename = "Owner")]
306    pub owner: Owner,
307    #[serde(rename = "AccessControlList")]
308    pub access_control_list: AccessControlList,
309}
310
311/// 所有者信息
312#[derive(Debug, Deserialize)]
313pub struct Owner {
314    #[serde(rename = "ID")]
315    pub id: String,
316    #[serde(rename = "DisplayName", default)]
317    pub display_name: String,
318}
319
320/// 访问控制列表
321#[derive(Debug, Deserialize)]
322pub struct AccessControlList {
323    #[serde(rename = "Grant", default)]
324    pub grants: Vec<Grant>,
325}
326
327/// 授权信息
328#[derive(Debug, Deserialize)]
329pub struct Grant {
330    #[serde(rename = "Grantee")]
331    pub grantee: Grantee,
332    #[serde(rename = "Permission")]
333    pub permission: String,
334}
335
336/// 被授权者
337#[derive(Debug, Deserialize)]
338pub struct Grantee {
339    #[serde(rename = "@type")]
340    pub grantee_type: String,
341    #[serde(rename = "ID", default)]
342    pub id: String,
343    #[serde(rename = "DisplayName", default)]
344    pub display_name: String,
345    #[serde(rename = "URI", default)]
346    pub uri: String,
347}
348
349/// 版本控制响应
350#[derive(Debug, Deserialize)]
351#[serde(rename = "VersioningConfiguration")]
352pub struct VersioningResponse {
353    #[serde(rename = "Status", default)]
354    pub status: String,
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360    use crate::config::Config;
361    use std::time::Duration;
362
363    #[tokio::test]
364    async fn test_bucket_operations() {
365        let config = Config::new("test_id", "test_key", "ap-beijing", "test-bucket-123")
366            .with_timeout(Duration::from_secs(60));
367        
368        let cos_client = CosClient::new(config).unwrap();
369        let bucket_client = BucketClient::new(cos_client);
370        
371        // 测试存储桶存在性检查
372        let exists = bucket_client.bucket_exists().await;
373        // 在实际测试中,这里会根据具体情况返回结果
374    }
375}