1use 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}