Skip to main content

aliyun_oss/operations/
bucket_inventory.rs

1//! Bucket inventory configuration operations.
2
3use std::sync::Arc;
4
5use crate::client::{BucketOperations, OSSClientInner};
6use crate::error::{ErrorContext, OssError, OssErrorKind, Result};
7use crate::http::client::HttpRequest;
8use crate::types::bucket::BucketName;
9
10pub struct PutBucketInventoryBuilder {
11    client: Arc<OSSClientInner>,
12    bucket: BucketName,
13    inventory_id: String,
14    body_xml: String,
15}
16
17impl PutBucketInventoryBuilder {
18    pub(crate) fn new(
19        client: Arc<OSSClientInner>,
20        bucket: BucketName,
21        inventory_id: String,
22        body_xml: String,
23    ) -> Self {
24        Self {
25            client,
26            bucket,
27            inventory_id,
28            body_xml,
29        }
30    }
31
32    pub async fn send(self) -> Result<PutBucketInventoryOutput> {
33        let endpoint = self.client.endpoint.clone();
34        let uri = format!(
35            "https://{}.{}?inventory&inventoryId={}",
36            self.bucket.as_str(),
37            endpoint,
38            self.inventory_id
39        );
40        let query_params: Vec<(String, String)> = vec![
41            ("inventory".into(), String::new()),
42            ("inventoryId".into(), self.inventory_id.clone()),
43        ];
44
45        let request = HttpRequest::builder()
46            .method(http::Method::PUT)
47            .uri(&uri)
48            .body(bytes::Bytes::from(self.body_xml))
49            .build();
50
51        let response = self
52            .client
53            .send_signed(request, Some(&self.bucket), query_params)
54            .await
55            .map_err(|e| OssError {
56                kind: OssErrorKind::TransportError,
57                context: Box::new(ErrorContext {
58                    operation: Some("PutBucketInventory".into()),
59                    bucket: Some(self.bucket.to_string()),
60                    endpoint: Some(endpoint),
61                    ..Default::default()
62                }),
63                source: Some(Box::new(e)),
64            })?;
65
66        if response.status().is_success() {
67            Ok(PutBucketInventoryOutput {
68                request_id: response
69                    .headers
70                    .get("x-oss-request-id")
71                    .and_then(|v| v.to_str().ok())
72                    .unwrap_or("")
73                    .to_string(),
74            })
75        } else {
76            Err(OssError {
77                kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
78                    status_code: response.status().as_u16(),
79                    code: String::new(),
80                    message: String::new(),
81                    request_id: String::new(),
82                    host_id: String::new(),
83                    resource: Some(self.bucket.to_string()),
84                    string_to_sign: None,
85                })),
86                context: Box::new(ErrorContext {
87                    operation: Some("PutBucketInventory".into()),
88                    bucket: Some(self.bucket.to_string()),
89                    ..Default::default()
90                }),
91                source: None,
92            })
93        }
94    }
95}
96
97#[derive(Debug, Clone)]
98pub struct PutBucketInventoryOutput {
99    pub request_id: String,
100}
101
102pub struct GetBucketInventoryBuilder {
103    client: Arc<OSSClientInner>,
104    bucket: BucketName,
105    inventory_id: String,
106}
107
108impl GetBucketInventoryBuilder {
109    pub(crate) fn new(
110        client: Arc<OSSClientInner>,
111        bucket: BucketName,
112        inventory_id: String,
113    ) -> Self {
114        Self {
115            client,
116            bucket,
117            inventory_id,
118        }
119    }
120
121    pub async fn send(self) -> Result<GetBucketInventoryOutput> {
122        let endpoint = self.client.endpoint.clone();
123        let uri = format!(
124            "https://{}.{}?inventory&inventoryId={}",
125            self.bucket.as_str(),
126            endpoint,
127            self.inventory_id
128        );
129        let query_params: Vec<(String, String)> = vec![
130            ("inventory".into(), String::new()),
131            ("inventoryId".into(), self.inventory_id),
132        ];
133
134        let request = HttpRequest::builder()
135            .method(http::Method::GET)
136            .uri(&uri)
137            .build();
138
139        let response = self
140            .client
141            .send_signed(request, Some(&self.bucket), query_params)
142            .await
143            .map_err(|e| OssError {
144                kind: OssErrorKind::TransportError,
145                context: Box::new(ErrorContext {
146                    operation: Some("GetBucketInventory".into()),
147                    bucket: Some(self.bucket.to_string()),
148                    endpoint: Some(endpoint),
149                    ..Default::default()
150                }),
151                source: Some(Box::new(e)),
152            })?;
153
154        if response.is_success() {
155            Ok(GetBucketInventoryOutput {
156                body: response.body_as_str().unwrap_or("").to_string(),
157            })
158        } else {
159            Err(OssError {
160                kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
161                    status_code: response.status().as_u16(),
162                    code: String::new(),
163                    message: String::new(),
164                    request_id: String::new(),
165                    host_id: String::new(),
166                    resource: Some(self.bucket.to_string()),
167                    string_to_sign: None,
168                })),
169                context: Box::new(ErrorContext {
170                    operation: Some("GetBucketInventory".into()),
171                    bucket: Some(self.bucket.to_string()),
172                    ..Default::default()
173                }),
174                source: None,
175            })
176        }
177    }
178}
179
180#[derive(Debug, Clone)]
181pub struct GetBucketInventoryOutput {
182    pub body: String,
183}
184
185pub struct DeleteBucketInventoryBuilder {
186    client: Arc<OSSClientInner>,
187    bucket: BucketName,
188    inventory_id: String,
189}
190
191impl DeleteBucketInventoryBuilder {
192    pub(crate) fn new(
193        client: Arc<OSSClientInner>,
194        bucket: BucketName,
195        inventory_id: String,
196    ) -> Self {
197        Self {
198            client,
199            bucket,
200            inventory_id,
201        }
202    }
203
204    pub async fn send(self) -> Result<DeleteBucketInventoryOutput> {
205        let endpoint = self.client.endpoint.clone();
206        let uri = format!(
207            "https://{}.{}?inventory&inventoryId={}",
208            self.bucket.as_str(),
209            endpoint,
210            self.inventory_id
211        );
212        let query_params: Vec<(String, String)> = vec![
213            ("inventory".into(), String::new()),
214            ("inventoryId".into(), self.inventory_id),
215        ];
216
217        let request = HttpRequest::builder()
218            .method(http::Method::DELETE)
219            .uri(&uri)
220            .build();
221
222        let response = self
223            .client
224            .send_signed(request, Some(&self.bucket), query_params)
225            .await
226            .map_err(|e| OssError {
227                kind: OssErrorKind::TransportError,
228                context: Box::new(ErrorContext {
229                    operation: Some("DeleteBucketInventory".into()),
230                    bucket: Some(self.bucket.to_string()),
231                    endpoint: Some(endpoint),
232                    ..Default::default()
233                }),
234                source: Some(Box::new(e)),
235            })?;
236
237        if response.status().is_success() {
238            Ok(DeleteBucketInventoryOutput {
239                request_id: response
240                    .headers
241                    .get("x-oss-request-id")
242                    .and_then(|v| v.to_str().ok())
243                    .unwrap_or("")
244                    .to_string(),
245            })
246        } else {
247            Err(OssError {
248                kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
249                    status_code: response.status().as_u16(),
250                    code: String::new(),
251                    message: String::new(),
252                    request_id: String::new(),
253                    host_id: String::new(),
254                    resource: Some(self.bucket.to_string()),
255                    string_to_sign: None,
256                })),
257                context: Box::new(ErrorContext {
258                    operation: Some("DeleteBucketInventory".into()),
259                    bucket: Some(self.bucket.to_string()),
260                    ..Default::default()
261                }),
262                source: None,
263            })
264        }
265    }
266}
267
268#[derive(Debug, Clone)]
269pub struct DeleteBucketInventoryOutput {
270    pub request_id: String,
271}
272
273pub struct ListBucketInventoryBuilder {
274    client: Arc<OSSClientInner>,
275    bucket: BucketName,
276}
277
278impl ListBucketInventoryBuilder {
279    pub(crate) fn new(client: Arc<OSSClientInner>, bucket: BucketName) -> Self {
280        Self { client, bucket }
281    }
282
283    pub async fn send(self) -> Result<ListBucketInventoryOutput> {
284        let endpoint = self.client.endpoint.clone();
285        let uri = format!("https://{}.{}?inventory", self.bucket.as_str(), endpoint);
286        let query_params: Vec<(String, String)> = vec![("inventory".into(), String::new())];
287
288        let request = HttpRequest::builder()
289            .method(http::Method::GET)
290            .uri(&uri)
291            .build();
292
293        let response = self
294            .client
295            .send_signed(request, Some(&self.bucket), query_params)
296            .await
297            .map_err(|e| OssError {
298                kind: OssErrorKind::TransportError,
299                context: Box::new(ErrorContext {
300                    operation: Some("ListBucketInventory".into()),
301                    bucket: Some(self.bucket.to_string()),
302                    endpoint: Some(endpoint),
303                    ..Default::default()
304                }),
305                source: Some(Box::new(e)),
306            })?;
307
308        if response.is_success() {
309            Ok(ListBucketInventoryOutput {
310                body: response.body_as_str().unwrap_or("").to_string(),
311            })
312        } else {
313            Err(OssError {
314                kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
315                    status_code: response.status().as_u16(),
316                    code: String::new(),
317                    message: String::new(),
318                    request_id: String::new(),
319                    host_id: String::new(),
320                    resource: Some(self.bucket.to_string()),
321                    string_to_sign: None,
322                })),
323                context: Box::new(ErrorContext {
324                    operation: Some("ListBucketInventory".into()),
325                    bucket: Some(self.bucket.to_string()),
326                    ..Default::default()
327                }),
328                source: None,
329            })
330        }
331    }
332}
333
334#[derive(Debug, Clone)]
335pub struct ListBucketInventoryOutput {
336    pub body: String,
337}
338
339impl BucketOperations {
340    pub fn put_inventory(
341        &self,
342        inventory_id: String,
343        body_xml: String,
344    ) -> PutBucketInventoryBuilder {
345        PutBucketInventoryBuilder::new(
346            self.client_inner().clone(),
347            self.bucket_name().clone(),
348            inventory_id,
349            body_xml,
350        )
351    }
352
353    pub fn get_inventory(&self, inventory_id: String) -> GetBucketInventoryBuilder {
354        GetBucketInventoryBuilder::new(
355            self.client_inner().clone(),
356            self.bucket_name().clone(),
357            inventory_id,
358        )
359    }
360
361    pub fn delete_inventory(&self, inventory_id: String) -> DeleteBucketInventoryBuilder {
362        DeleteBucketInventoryBuilder::new(
363            self.client_inner().clone(),
364            self.bucket_name().clone(),
365            inventory_id,
366        )
367    }
368
369    pub fn list_inventory(&self) -> ListBucketInventoryBuilder {
370        ListBucketInventoryBuilder::new(self.client_inner().clone(), self.bucket_name().clone())
371    }
372}
373
374#[cfg(test)]
375mod tests {
376    use std::sync::Mutex;
377
378    use crate::client::OSSClientInner;
379    use crate::config::credentials::Credentials;
380    use crate::http::client::{HttpClient, HttpRequest, HttpResponse};
381    use crate::types::region::Region;
382
383    use super::*;
384
385    struct RecordingHttpClient {
386        requests: Arc<Mutex<Vec<HttpRequest>>>,
387        status_code: http::StatusCode,
388        response_body: bytes::Bytes,
389    }
390
391    #[async_trait::async_trait]
392    impl HttpClient for RecordingHttpClient {
393        async fn send(&self, request: HttpRequest) -> crate::error::Result<HttpResponse> {
394            self.requests.lock().unwrap().push(request);
395            let mut headers = http::HeaderMap::new();
396            headers.insert(
397                "x-oss-request-id",
398                http::HeaderValue::from_static("rid-inv"),
399            );
400            Ok(HttpResponse {
401                status: self.status_code,
402                headers,
403                body: self.response_body.clone(),
404            })
405        }
406    }
407
408    fn create_test_inner(
409        body: bytes::Bytes,
410    ) -> (Arc<OSSClientInner>, Arc<Mutex<Vec<HttpRequest>>>) {
411        let requests = Arc::new(Mutex::new(Vec::new()));
412        let http = Arc::new(RecordingHttpClient {
413            requests: requests.clone(),
414            status_code: http::StatusCode::OK,
415            response_body: body,
416        });
417        let credentials = Arc::new(crate::config::credentials::StaticCredentialsProvider::new(
418            Credentials::builder()
419                .access_key_id("test-ak")
420                .access_key_secret("test-sk")
421                .build()
422                .unwrap(),
423        ));
424        let inner = Arc::new(OSSClientInner {
425            http,
426            credentials,
427            signer: Arc::from(crate::signer::create_signer(crate::signer::SignVersion::V4)),
428            region: Region::CnHangzhou,
429            endpoint: "oss-cn-hangzhou.aliyuncs.com".into(),
430        });
431        (inner, requests)
432    }
433
434    #[tokio::test]
435    async fn put_inventory_sends_with_inventory_id() {
436        let (inner, requests) = create_test_inner(bytes::Bytes::new());
437        let builder = PutBucketInventoryBuilder::new(
438            inner,
439            BucketName::new("test-bucket").unwrap(),
440            "report1".into(),
441            "<InventoryConfiguration/>".into(),
442        );
443        builder.send().await.unwrap();
444        let captured = requests.lock().unwrap();
445        assert_eq!(captured[0].method, http::Method::PUT);
446        assert!(captured[0].uri.contains("?inventory"));
447    }
448
449    #[tokio::test]
450    async fn list_inventory_sends_get() {
451        let (inner, requests) =
452            create_test_inner(bytes::Bytes::from("<ListInventoryConfigurationsResult/>"));
453        let builder =
454            ListBucketInventoryBuilder::new(inner, BucketName::new("test-bucket").unwrap());
455        let _output = builder.send().await.unwrap();
456        let captured = requests.lock().unwrap();
457        assert_eq!(captured[0].method, http::Method::GET);
458    }
459}