1use crate::client::CosClient;
6use crate::error::{CosError, Result};
7use serde::Deserialize;
8use std::collections::HashMap;
9
10#[derive(Debug, Clone)]
12pub struct BucketClient {
13 client: CosClient,
14}
15
16impl BucketClient {
17 pub fn new(client: CosClient) -> Self {
19 Self { client }
20 }
21
22 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 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 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 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 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 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 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 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 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#[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#[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#[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#[derive(Debug, Deserialize)]
229#[serde(rename = "LocationConstraint")]
230struct LocationResponse {
231 #[serde(rename = "$text", default)]
232 location_constraint: String,
233}
234
235#[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#[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#[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#[derive(Debug, Deserialize)]
296pub struct CommonPrefix {
297 #[serde(rename = "Prefix")]
298 pub prefix: String,
299}
300
301#[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#[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#[derive(Debug, Deserialize)]
322pub struct AccessControlList {
323 #[serde(rename = "Grant", default)]
324 pub grants: Vec<Grant>,
325}
326
327#[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#[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#[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 let exists = bucket_client.bucket_exists().await;
373 }
375}