Skip to main content

u_sdk/oss/
bucket.rs

1//! 只实现了小部分API
2//!
3//! [阿里云API文档](https://help.aliyun.com/zh/oss/developer-reference/bucket-operations/)
4
5use super::Client;
6use super::sign_v4::HTTPVerb;
7use super::utils::{get_request_header, into_request_failed_error, parse_xml_response};
8use crate::oss::Error;
9use bon::Builder;
10use serde::{Deserialize, Serialize};
11use serde_with::{DisplayFromStr, serde_as};
12use std::collections::HashMap;
13use url::Url;
14
15// region:    --- put bucket
16#[serde_with::skip_serializing_none]
17#[derive(Builder, Serialize)]
18#[serde(rename_all = "kebab-case")]
19pub struct PutBucket<'a> {
20    #[builder(start_fn)]
21    #[serde(skip_serializing)]
22    pub(crate) client: &'a Client,
23    #[serde(skip_serializing)]
24    pub(crate) bucket_name: &'a str,
25    // 请求参数
26    #[serde(skip_serializing)]
27    pub(crate) storage_class: Option<&'a str>,
28    #[serde(skip_serializing)]
29    pub(crate) data_redundancy_type: Option<&'a str>,
30    // header
31    x_oss_acl: Option<&'a str>,
32    x_oss_resource_group_id: Option<&'a str>,
33    x_oss_bucket_tagging: Option<&'a str>,
34}
35
36#[serde_with::skip_serializing_none]
37#[derive(Serialize)]
38#[serde(rename_all = "PascalCase")]
39struct CreateBucketConfiguration<'a> {
40    /// 默认为`Standard`
41    storage_class: Option<&'a str>,
42    /// 默认为`LRS`
43    data_redundancy_type: Option<&'a str>,
44}
45
46impl PutBucket<'_> {
47    pub async fn send(&self) -> Result<(), Error> {
48        let client = self.client;
49        let request_url =
50            Url::parse(&format!("https://{}.{}", self.bucket_name, client.endpoint)).unwrap();
51        let mut req_header_map: HashMap<String, String> =
52            serde_json::from_value(serde_json::to_value(self).unwrap()).unwrap();
53
54        let creds = client.credentials_provider.load().await?;
55        if let Some(token) = &creds.sts_security_token {
56            req_header_map.insert("x-oss-security-token".to_string(), token.clone());
57        }
58
59        let header = get_request_header(
60            &creds.access_key_id,
61            &creds.access_key_secret,
62            req_header_map,
63            &request_url,
64            HTTPVerb::Put,
65            &client.region,
66            Some(self.bucket_name),
67        );
68
69        let req_xml = {
70            let create_conf = CreateBucketConfiguration {
71                storage_class: self.storage_class,
72                data_redundancy_type: self.data_redundancy_type,
73            };
74
75            quick_xml::se::to_string(&create_conf).unwrap()
76        };
77
78        let resp = client
79            .http_client
80            .put(request_url)
81            .headers(header)
82            .body(req_xml)
83            .send()
84            .await?;
85
86        if !resp.status().is_success() {
87            return Err(into_request_failed_error(resp).await);
88        }
89
90        Ok(())
91    }
92}
93// endregion: --- put bucket
94
95// region:    --- list objects v2
96/// `list-type`将自动设为2
97#[serde_as]
98#[serde_with::skip_serializing_none]
99#[derive(Builder, Serialize)]
100#[serde(rename_all = "kebab-case")]
101pub struct ListObjectsV2<'a> {
102    #[builder(start_fn)]
103    #[serde(skip_serializing)]
104    pub(crate) client: &'a Client,
105    // list-type: 2  固定的,自动添加
106    delimiter: Option<&'a str>,
107    start_after: Option<&'a str>,
108    continuation_token: Option<&'a str>,
109    #[serde_as(as = "Option<DisplayFromStr>")]
110    max_keys: Option<u16>,
111    prefix: Option<&'a str>,
112    encoding_type: Option<&'a str>,
113    #[serde_as(as = "Option<DisplayFromStr>")]
114    fetch_owner: Option<bool>,
115}
116
117#[derive(Deserialize, Debug)]
118#[serde(rename_all = "PascalCase")]
119pub struct ListBucketResult {
120    pub contents: Option<Vec<Content>>,
121    pub common_prefixes: Option<CommonPrefixes>,
122    pub delimiter: String,
123    pub encoding_type: Option<String>,
124    pub is_truncated: bool,
125    pub start_after: Option<String>,
126    pub max_keys: u16,
127    pub name: String,
128    pub prefix: String,
129    pub continuation_token: Option<u32>,
130    pub key_count: u32,
131    pub next_continuation_token: Option<String>,
132}
133
134#[derive(Deserialize, Debug)]
135#[serde(rename_all = "PascalCase")]
136pub struct CommonPrefixes {
137    pub prefix: String,
138}
139
140#[derive(Deserialize, Debug)]
141#[serde(rename_all = "PascalCase")]
142pub struct Content {
143    pub owner: Option<Owner>,
144    pub e_tag: String,
145    pub key: String,
146    pub last_modified: String,
147    pub size: u32,
148    pub storage_class: String,
149    pub restore_info: Option<String>,
150    pub r#type: String,
151}
152
153#[derive(Deserialize, Debug)]
154#[serde(rename_all = "PascalCase")]
155pub struct Owner {
156    pub display_name: String,
157    #[serde(rename = "ID")]
158    pub id: String,
159}
160
161impl ListObjectsV2<'_> {
162    pub async fn send(&self) -> Result<ListBucketResult, Error> {
163        let mut query_map: HashMap<String, String> =
164            serde_json::from_value(serde_json::to_value(self).unwrap()).unwrap();
165        // 添加固定的query参数
166        query_map.insert("list-type".to_owned(), "2".to_owned());
167
168        let client = self.client;
169        let sign_url = Url::parse_with_params(
170            &format!("https://{}.{}/", client.bucket, client.endpoint),
171            query_map,
172        )
173        .unwrap();
174
175        let creds = client.credentials_provider.load().await?;
176        let mut req_header_map = HashMap::new();
177        if let Some(token) = &creds.sts_security_token {
178            req_header_map.insert("x-oss-security-token".to_string(), token.clone());
179        }
180
181        let header = get_request_header(
182            &creds.access_key_id,
183            &creds.access_key_secret,
184            req_header_map,
185            &sign_url,
186            HTTPVerb::Get,
187            &client.region,
188            Some(&client.bucket),
189        );
190
191        let resp = client
192            .http_client
193            .get(sign_url)
194            .headers(header)
195            .send()
196            .await?;
197
198        let res = parse_xml_response(resp).await?;
199        Ok(res)
200    }
201}
202// endregion: --- list objects v2
203
204// region:    --- get bucket info
205#[derive(Builder)]
206pub struct GetBucketInfo<'a> {
207    #[builder(start_fn)]
208    pub(crate) client: &'a Client,
209    pub(crate) bucket: &'a str,
210}
211
212#[derive(Deserialize, Debug)]
213#[serde(rename_all = "PascalCase")]
214pub struct BucketInfo {
215    pub bucket: Bucket,
216}
217
218#[derive(Deserialize, Debug)]
219#[serde(rename_all = "PascalCase")]
220pub struct Bucket {
221    pub creation_date: String,
222    pub extranet_endpoint: String,
223    pub intranet_endpoint: String,
224    pub location: String,
225    pub storage_class: String,
226    pub name: String,
227    pub resource_group_id: String,
228    pub owner: Owner,
229    pub access_control_list: AccessControlList,
230    pub data_redundancy_type: String,
231    pub versioning: Option<String>,
232    pub cross_region_replication: String,
233    pub transfer_acceleration: String,
234    pub access_monitor: String,
235    pub bucket_policy: BucketPolicy,
236    pub comment: String,
237    pub server_side_encryption_rule: ServerSideEncryptionRule,
238    pub block_public_access: bool,
239}
240
241#[derive(Deserialize, Debug)]
242pub struct ServerSideEncryptionRule {
243    #[serde(rename = "SSEAlgorithm")]
244    pub sse_algorithm: String,
245    pub kms_master_key_id: Option<String>,
246    pub kms_data_encryption: Option<String>,
247}
248
249#[derive(Deserialize, Debug)]
250#[serde(rename_all = "PascalCase")]
251pub struct AccessControlList {
252    pub grant: String,
253}
254
255#[derive(Deserialize, Debug)]
256#[serde(rename_all = "PascalCase")]
257pub struct BucketPolicy {
258    pub log_bucket: String,
259    pub log_prefix: String,
260}
261
262impl GetBucketInfo<'_> {
263    pub async fn send(&self) -> Result<BucketInfo, Error> {
264        let client = self.client;
265        let request_url = Url::parse_with_params(
266            &format!("https://{}.{}", self.bucket, client.endpoint),
267            [("bucketInfo", "")],
268        )
269        .unwrap();
270
271        let creds = client.credentials_provider.load().await?;
272        let mut req_header_map = HashMap::new();
273        if let Some(token) = &creds.sts_security_token {
274            req_header_map.insert("x-oss-security-token".to_string(), token.clone());
275        }
276
277        let header_map = get_request_header(
278            &creds.access_key_id,
279            &creds.access_key_secret,
280            req_header_map,
281            &request_url,
282            HTTPVerb::Get,
283            &client.region,
284            Some(&client.bucket),
285        );
286        let resp = client
287            .http_client
288            .get(request_url)
289            .headers(header_map)
290            .send()
291            .await?;
292
293        let res = parse_xml_response(resp).await?;
294        Ok(res)
295    }
296}
297// endregion: --- get bucket info
298
299//region get bucket location
300#[derive(Builder)]
301pub struct GetBucketLocation<'a> {
302    #[builder(start_fn)]
303    pub(crate) client: &'a Client,
304    pub(crate) bucket: &'a str,
305}
306// xml数据为:"<LocationConstraint>oss-cn-hangzhou</LocationConstraint>",
307// 这种情况下使用xml反序列化比较特殊,写法得类似于下面这样:
308#[derive(Deserialize)]
309struct LocationConstraint {
310    #[serde(rename = "$text")]
311    field: String,
312}
313
314impl GetBucketLocation<'_> {
315    pub async fn send(&self) -> Result<String, Error> {
316        let client = self.client;
317
318        let request_url = Url::parse_with_params(
319            &format!("https://{}.{}", self.bucket, client.endpoint),
320            [("location", "")],
321        )
322        .unwrap();
323
324        let creds = client.credentials_provider.load().await?;
325        let mut req_header_map = HashMap::new();
326        if let Some(token) = &creds.sts_security_token {
327            req_header_map.insert("x-oss-security-token".to_string(), token.clone());
328        }
329
330        let header_map = get_request_header(
331            &creds.access_key_id,
332            &creds.access_key_secret,
333            req_header_map,
334            &request_url,
335            HTTPVerb::Get,
336            &client.region,
337            Some(&client.bucket),
338        );
339        let resp = client
340            .http_client
341            .get(request_url)
342            .headers(header_map)
343            .send()
344            .await?;
345
346        let res = parse_xml_response::<LocationConstraint>(resp).await?;
347        Ok(res.field)
348    }
349}
350//endregion
351
352// region:    --- get bucket stat
353#[derive(Builder)]
354pub struct GetBucketStat<'a> {
355    #[builder(start_fn)]
356    pub(crate) client: &'a Client,
357    pub(crate) bucket: &'a str,
358}
359
360#[derive(Deserialize, Debug)]
361#[serde(rename_all = "PascalCase")]
362pub struct BucketStat {
363    pub storage: u64,
364    pub object_count: u32,
365    pub multipart_upload_count: u32,
366    pub delete_marker_count: u32,
367    pub live_channel_count: u32,
368    pub last_modified_time: u64,
369    pub standard_storage: u64,
370    pub standard_object_count: u32,
371    pub infrequent_access_storage: u64,
372    pub infrequent_access_real_storage: u64,
373    pub infrequent_access_object_count: u32,
374    pub archive_storage: u64,
375    pub archive_real_storage: u64,
376    pub archive_object_count: u32,
377    pub cold_archive_storage: u64,
378    pub cold_archive_real_storage: u64,
379    pub cold_archive_object_count: u32,
380    pub deep_cold_archive_storage: u64,
381    pub deep_cold_archive_real_storage: u64,
382    pub deep_cold_archive_object_count: u32,
383}
384
385impl GetBucketStat<'_> {
386    pub async fn send(&self) -> Result<BucketStat, Error> {
387        let client = self.client;
388        let request_url = Url::parse_with_params(
389            &format!("https://{}.{}", self.bucket, client.endpoint),
390            [("stat", "")],
391        )
392        .unwrap();
393
394        let creds = client.credentials_provider.load().await?;
395        let mut req_header_map = HashMap::new();
396        if let Some(token) = &creds.sts_security_token {
397            req_header_map.insert("x-oss-security-token".to_string(), token.clone());
398        }
399
400        let header_map = get_request_header(
401            &creds.access_key_id,
402            &creds.access_key_secret,
403            req_header_map,
404            &request_url,
405            HTTPVerb::Get,
406            &client.region,
407            Some(&client.bucket),
408        );
409
410        let resp = client
411            .http_client
412            .get(request_url)
413            .headers(header_map)
414            .send()
415            .await?;
416
417        let res = parse_xml_response(resp).await?;
418        Ok(res)
419    }
420}
421// endregion: --- get bucket stat
422
423impl Client {
424    pub fn put_bucket(&self) -> PutBucketBuilder<'_> {
425        PutBucket::builder(self)
426    }
427
428    pub fn list_objects_v2(&self) -> ListObjectsV2Builder<'_> {
429        ListObjectsV2::builder(self)
430    }
431
432    pub fn get_bucket_info(&self) -> GetBucketInfoBuilder<'_> {
433        GetBucketInfo::builder(self)
434    }
435
436    pub fn get_bucket_location(&self) -> GetBucketLocationBuilder<'_> {
437        GetBucketLocation::builder(self)
438    }
439
440    pub fn get_bucket_stat(&self) -> GetBucketStatBuilder<'_> {
441        GetBucketStat::builder(self)
442    }
443}