1use serde::Deserialize;
4
5use super::bucket::BucketName;
6use super::object::{ETag, ObjectKey};
7
8#[derive(Debug, Clone, Deserialize, PartialEq)]
10#[serde(rename = "ListBucketResult")]
11pub struct ListObjectsOutput {
12 #[serde(rename = "Name")]
13 pub name: String,
14
15 #[serde(rename = "Prefix", default)]
16 pub prefix: String,
17
18 #[serde(rename = "Marker", default)]
19 pub marker: String,
20
21 #[serde(rename = "MaxKeys")]
22 pub max_keys: i32,
23
24 #[serde(rename = "IsTruncated")]
25 pub is_truncated: bool,
26
27 #[serde(rename = "NextMarker", default)]
28 pub next_marker: String,
29
30 #[serde(rename = "Delimiter", default)]
31 pub delimiter: String,
32
33 #[serde(rename = "EncodingType", default)]
34 pub encoding_type: String,
35
36 #[serde(rename = "CommonPrefixes", default)]
37 pub common_prefixes: Vec<CommonPrefix>,
38
39 #[serde(rename = "Contents", default)]
40 pub objects: Vec<ObjectSummary>,
41}
42
43impl ListObjectsOutput {
44 pub fn bucket_name(&self) -> Option<BucketName> {
46 BucketName::new(&self.name).ok()
47 }
48
49 pub fn object_keys(&self) -> Vec<ObjectKey> {
51 self.objects
52 .iter()
53 .filter_map(|o| ObjectKey::new(&o.key).ok())
54 .collect()
55 }
56}
57
58#[derive(Debug, Clone, Deserialize, PartialEq)]
60pub struct CommonPrefix {
61 #[serde(rename = "Prefix")]
62 pub prefix: String,
63}
64
65#[derive(Debug, Clone, Deserialize, PartialEq)]
67pub struct ObjectSummary {
68 #[serde(rename = "Key")]
69 pub key: String,
70
71 #[serde(rename = "LastModified")]
72 pub last_modified: String,
73
74 #[serde(rename = "ETag")]
75 pub etag: String,
76
77 #[serde(rename = "Size")]
78 pub size: u64,
79
80 #[serde(rename = "StorageClass")]
81 pub storage_class: String,
82
83 #[serde(rename = "Owner", default)]
84 pub owner: Option<OwnerInfo>,
85}
86
87impl ObjectSummary {
88 pub fn etag_parsed(&self) -> Option<ETag> {
90 ETag::from_header(&self.etag)
91 }
92
93 pub fn object_key(&self) -> Option<ObjectKey> {
95 ObjectKey::new(&self.key).ok()
96 }
97}
98
99#[derive(Debug, Clone, Deserialize, PartialEq)]
101pub struct OwnerInfo {
102 #[serde(rename = "ID")]
103 pub id: String,
104
105 #[serde(rename = "DisplayName", default)]
106 pub display_name: String,
107}
108
109#[derive(Debug, Clone, Deserialize, PartialEq)]
111#[serde(rename = "ListBucketResult")]
112pub struct ListObjectsV2Output {
113 #[serde(rename = "Name")]
114 pub name: String,
115
116 #[serde(rename = "Prefix", default)]
117 pub prefix: String,
118
119 #[serde(rename = "StartAfter", default)]
120 pub start_after: String,
121
122 #[serde(rename = "MaxKeys")]
123 pub max_keys: i32,
124
125 #[serde(rename = "IsTruncated")]
126 pub is_truncated: bool,
127
128 #[serde(rename = "NextContinuationToken", default)]
129 pub next_continuation_token: String,
130
131 #[serde(rename = "ContinuationToken", default)]
132 pub continuation_token: String,
133
134 #[serde(rename = "KeyCount", default)]
135 pub key_count: i32,
136
137 #[serde(rename = "Delimiter", default)]
138 pub delimiter: String,
139
140 #[serde(rename = "EncodingType", default)]
141 pub encoding_type: String,
142
143 #[serde(rename = "CommonPrefixes", default)]
144 pub common_prefixes: Vec<CommonPrefix>,
145
146 #[serde(rename = "Contents", default)]
147 pub objects: Vec<ObjectSummary>,
148}
149
150#[derive(Debug, Clone, Deserialize, PartialEq)]
152#[serde(rename = "ListAllMyBucketsResult")]
153pub struct ListBucketsOutput {
154 #[serde(rename = "Owner")]
155 pub owner: OwnerInfo,
156
157 #[serde(rename = "Buckets", default)]
158 pub buckets: BucketsContainer,
159}
160
161#[derive(Debug, Clone, Default, Deserialize, PartialEq)]
163pub struct BucketsContainer {
164 #[serde(rename = "Bucket", default)]
165 pub bucket: Vec<BucketSummary>,
166}
167
168#[derive(Debug, Clone, Deserialize, PartialEq)]
170pub struct BucketSummary {
171 #[serde(rename = "Name")]
172 pub name: String,
173
174 #[serde(rename = "CreationDate")]
175 pub creation_date: String,
176
177 #[serde(rename = "Location")]
178 pub location: String,
179
180 #[serde(rename = "ExtranetEndpoint")]
181 pub extranet_endpoint: String,
182
183 #[serde(rename = "IntranetEndpoint")]
184 pub intranet_endpoint: String,
185
186 #[serde(rename = "StorageClass")]
187 pub storage_class: String,
188
189 #[serde(rename = "Region", default)]
190 pub region: String,
191}
192
193#[derive(Debug, Clone, Deserialize, PartialEq)]
195#[serde(rename = "BucketInfo")]
196pub struct GetBucketInfoOutput {
197 #[serde(rename = "Bucket")]
198 pub bucket: BucketInfoDetail,
199}
200
201#[derive(Debug, Clone, Deserialize, PartialEq)]
203pub struct BucketInfoDetail {
204 #[serde(rename = "Name")]
205 pub name: String,
206
207 #[serde(rename = "CreationDate")]
208 pub creation_date: String,
209
210 #[serde(rename = "Location")]
211 pub location: String,
212
213 #[serde(rename = "ExtranetEndpoint")]
214 pub extranet_endpoint: String,
215
216 #[serde(rename = "IntranetEndpoint")]
217 pub intranet_endpoint: String,
218
219 #[serde(rename = "StorageClass")]
220 pub storage_class: String,
221
222 #[serde(rename = "Owner")]
223 pub owner: OwnerInfo,
224
225 #[serde(rename = "AccessControlList", default)]
226 pub acl: Option<AccessControlList>,
227
228 #[serde(rename = "DataRedundancyType", default)]
229 pub data_redundancy_type: String,
230
231 #[serde(rename = "Versioning", default)]
232 pub versioning: String,
233}
234
235#[derive(Debug, Clone, Deserialize, PartialEq)]
237pub struct AccessControlList {
238 #[serde(rename = "Grant")]
239 pub grant: String,
240}
241
242#[derive(Debug, Clone, Deserialize, PartialEq)]
244#[serde(rename = "BucketStat")]
245pub struct GetBucketStatOutput {
246 #[serde(rename = "Storage")]
247 pub storage: u64,
248
249 #[serde(rename = "ObjectCount")]
250 pub object_count: u64,
251
252 #[serde(rename = "MultipartUploadCount")]
253 pub multipart_upload_count: u64,
254
255 #[serde(rename = "LiveChannelCount")]
256 pub live_channel_count: u64,
257
258 #[serde(rename = "LastModifiedTime")]
259 pub last_modified_time: u64,
260
261 #[serde(rename = "StandardStorage")]
262 pub standard_storage: u64,
263
264 #[serde(rename = "StandardObjectCount")]
265 pub standard_object_count: u64,
266
267 #[serde(rename = "InfrequentAccessStorage")]
268 pub ia_storage: u64,
269
270 #[serde(rename = "InfrequentAccessObjectCount")]
271 pub ia_object_count: u64,
272
273 #[serde(rename = "ArchiveStorage")]
274 pub archive_storage: u64,
275
276 #[serde(rename = "ArchiveObjectCount")]
277 pub archive_object_count: u64,
278
279 #[serde(rename = "ColdArchiveStorage")]
280 pub cold_archive_storage: u64,
281
282 #[serde(rename = "ColdArchiveObjectCount")]
283 pub cold_archive_object_count: u64,
284
285 #[serde(rename = "DeepColdArchiveStorage")]
286 pub deep_cold_archive_storage: u64,
287
288 #[serde(rename = "DeepColdArchiveObjectCount")]
289 pub deep_cold_archive_object_count: u64,
290}
291
292#[derive(Debug, Clone, Deserialize, PartialEq)]
294#[serde(rename = "AccessControlPolicy")]
295pub struct GetObjectAclOutput {
296 #[serde(rename = "Owner")]
297 pub owner: OwnerInfo,
298 #[serde(rename = "AccessControlList")]
299 pub acl: AccessControlPolicyDetail,
300}
301
302#[derive(Debug, Clone, Deserialize, PartialEq)]
304pub struct AccessControlPolicyDetail {
305 #[serde(rename = "Grant")]
306 pub grant: String,
307}
308
309#[derive(Debug, Clone, Default, Deserialize, PartialEq)]
311#[serde(rename = "DeleteResult")]
312pub struct DeleteResult {
313 #[serde(rename = "Deleted", default)]
314 pub deleted: Vec<DeletedObject>,
315}
316
317#[derive(Debug, Clone, Deserialize, PartialEq)]
319pub struct DeletedObject {
320 #[serde(rename = "Key")]
321 pub key: String,
322 #[serde(rename = "DeleteMarker", default)]
323 pub delete_marker: bool,
324 #[serde(rename = "DeleteMarkerVersionId", default)]
325 pub delete_marker_version_id: String,
326 #[serde(rename = "VersionId", default)]
327 pub version_id: String,
328}
329
330#[derive(Debug, Clone, Deserialize, PartialEq)]
332#[serde(rename = "ListVersionsResult")]
333pub struct ListVersionsOutput {
334 #[serde(rename = "Name")]
335 pub name: String,
336 #[serde(rename = "Prefix", default)]
337 pub prefix: String,
338 #[serde(rename = "KeyMarker", default)]
339 pub key_marker: String,
340 #[serde(rename = "VersionIdMarker", default)]
341 pub version_id_marker: String,
342 #[serde(rename = "MaxKeys")]
343 pub max_keys: i32,
344 #[serde(rename = "IsTruncated")]
345 pub is_truncated: bool,
346 #[serde(rename = "NextKeyMarker", default)]
347 pub next_key_marker: String,
348 #[serde(rename = "NextVersionIdMarker", default)]
349 pub next_version_id_marker: String,
350 #[serde(rename = "Version", default)]
351 pub versions: Vec<ObjectVersion>,
352 #[serde(rename = "DeleteMarker", default)]
353 pub delete_markers: Vec<DeleteMarkerSummary>,
354 #[serde(rename = "CommonPrefixes", default)]
355 pub common_prefixes: Vec<CommonPrefix>,
356}
357
358#[derive(Debug, Clone, Deserialize, PartialEq)]
360pub struct ObjectVersion {
361 #[serde(rename = "Key")]
362 pub key: String,
363 #[serde(rename = "VersionId")]
364 pub version_id: String,
365 #[serde(rename = "IsLatest")]
366 pub is_latest: bool,
367 #[serde(rename = "LastModified")]
368 pub last_modified: String,
369 #[serde(rename = "ETag")]
370 pub etag: String,
371 #[serde(rename = "Size")]
372 pub size: u64,
373 #[serde(rename = "StorageClass")]
374 pub storage_class: String,
375 #[serde(rename = "Owner", default)]
376 pub owner: Option<OwnerInfo>,
377}
378
379#[derive(Debug, Clone, Deserialize, PartialEq)]
381pub struct DeleteMarkerSummary {
382 #[serde(rename = "Key")]
383 pub key: String,
384 #[serde(rename = "VersionId")]
385 pub version_id: String,
386 #[serde(rename = "IsLatest")]
387 pub is_latest: bool,
388 #[serde(rename = "LastModified")]
389 pub last_modified: String,
390 #[serde(rename = "Owner", default)]
391 pub owner: Option<OwnerInfo>,
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397 use crate::util::xml::from_xml;
398
399 #[test]
400 fn list_objects_output_parsed_from_xml() {
401 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
402 <ListBucketResult>
403 <Name>examplebucket</Name>
404 <Prefix>test</Prefix>
405 <Marker></Marker>
406 <MaxKeys>100</MaxKeys>
407 <IsTruncated>false</IsTruncated>
408 <Contents>
409 <Key>test/file1.txt</Key>
410 <LastModified>2024-01-01T00:00:00.000Z</LastModified>
411 <ETag>"abc123"</ETag>
412 <Size>1024</Size>
413 <StorageClass>Standard</StorageClass>
414 <Owner>
415 <ID>owner-id</ID>
416 <DisplayName>owner</DisplayName>
417 </Owner>
418 </Contents>
419 </ListBucketResult>"#;
420
421 let result: ListObjectsOutput = from_xml(xml).unwrap();
422 assert_eq!(result.name, "examplebucket");
423 assert_eq!(result.objects.len(), 1);
424 assert_eq!(result.objects[0].key, "test/file1.txt");
425 assert_eq!(result.objects[0].size, 1024);
426 assert_eq!(result.objects[0].etag_parsed().unwrap().as_str(), "abc123");
427 }
428
429 #[test]
430 fn list_objects_truncated_with_next_marker() {
431 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
432 <ListBucketResult>
433 <Name>bucket</Name>
434 <MaxKeys>2</MaxKeys>
435 <IsTruncated>true</IsTruncated>
436 <NextMarker>next-token</NextMarker>
437 <Contents>
438 <Key>obj1</Key>
439 <LastModified>2024-01-01T00:00:00.000Z</LastModified>
440 <ETag>"etag1"</ETag>
441 <Size>100</Size>
442 <StorageClass>Standard</StorageClass>
443 </Contents>
444 </ListBucketResult>"#;
445
446 let result: ListObjectsOutput = from_xml(xml).unwrap();
447 assert!(result.is_truncated);
448 assert_eq!(result.next_marker, "next-token");
449 }
450
451 #[test]
452 fn common_prefixes_parsed_correctly() {
453 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
454 <ListBucketResult>
455 <Name>bucket</Name>
456 <MaxKeys>100</MaxKeys>
457 <IsTruncated>false</IsTruncated>
458 <Prefix>dir/</Prefix>
459 <Delimiter>/</Delimiter>
460 <CommonPrefixes>
461 <Prefix>dir/subdir1/</Prefix>
462 </CommonPrefixes>
463 <CommonPrefixes>
464 <Prefix>dir/subdir2/</Prefix>
465 </CommonPrefixes>
466 </ListBucketResult>"#;
467
468 let result: ListObjectsOutput = from_xml(xml).unwrap();
469 assert_eq!(result.common_prefixes.len(), 2);
470 assert_eq!(result.common_prefixes[0].prefix, "dir/subdir1/");
471 assert_eq!(result.common_prefixes[1].prefix, "dir/subdir2/");
472 }
473
474 #[test]
475 fn list_buckets_output_parsed_from_xml() {
476 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
477 <ListAllMyBucketsResult>
478 <Owner>
479 <ID>owner-id</ID>
480 <DisplayName>owner-name</DisplayName>
481 </Owner>
482 <Buckets>
483 <Bucket>
484 <Name>bucket1</Name>
485 <CreationDate>2024-01-01T00:00:00.000Z</CreationDate>
486 <Location>oss-cn-hangzhou</Location>
487 <ExtranetEndpoint>oss-cn-hangzhou.aliyuncs.com</ExtranetEndpoint>
488 <IntranetEndpoint>oss-cn-hangzhou-internal.aliyuncs.com</IntranetEndpoint>
489 <StorageClass>Standard</StorageClass>
490 </Bucket>
491 </Buckets>
492 </ListAllMyBucketsResult>"#;
493
494 let result: ListBucketsOutput = from_xml(xml).unwrap();
495 assert_eq!(result.owner.id, "owner-id");
496 assert_eq!(result.buckets.bucket.len(), 1);
497 assert_eq!(result.buckets.bucket[0].name, "bucket1");
498 }
499
500 #[test]
501 fn list_objects_v2_output_parsed_from_xml() {
502 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
503 <ListBucketResult>
504 <Name>bucket</Name>
505 <MaxKeys>100</MaxKeys>
506 <IsTruncated>false</IsTruncated>
507 <KeyCount>2</KeyCount>
508 <ContinuationToken>token1</ContinuationToken>
509 <Contents>
510 <Key>obj1</Key>
511 <LastModified>2024-01-01T00:00:00.000Z</LastModified>
512 <ETag>"etag1"</ETag>
513 <Size>100</Size>
514 <StorageClass>Standard</StorageClass>
515 </Contents>
516 </ListBucketResult>"#;
517
518 let result: ListObjectsV2Output = from_xml(xml).unwrap();
519 assert_eq!(result.key_count, 2);
520 assert_eq!(result.continuation_token, "token1");
521 }
522
523 #[test]
524 fn get_bucket_info_output_parsed_from_xml() {
525 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
526 <BucketInfo>
527 <Bucket>
528 <Name>my-bucket</Name>
529 <CreationDate>2024-01-01T00:00:00.000Z</CreationDate>
530 <Location>oss-cn-hangzhou</Location>
531 <StorageClass>Standard</StorageClass>
532 <ExtranetEndpoint>oss-cn-hangzhou.aliyuncs.com</ExtranetEndpoint>
533 <IntranetEndpoint>oss-cn-hangzhou-internal.aliyuncs.com</IntranetEndpoint>
534 <Owner>
535 <ID>owner-id</ID>
536 </Owner>
537 <AccessControlList>
538 <Grant>private</Grant>
539 </AccessControlList>
540 </Bucket>
541 </BucketInfo>"#;
542
543 let result: GetBucketInfoOutput = from_xml(xml).unwrap();
544 assert_eq!(result.bucket.name, "my-bucket");
545 assert!(result.bucket.acl.is_some());
546 }
547
548 #[test]
549 fn get_bucket_stat_output_parsed_from_xml() {
550 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
551 <BucketStat>
552 <Storage>1024</Storage>
553 <ObjectCount>10</ObjectCount>
554 <MultipartUploadCount>2</MultipartUploadCount>
555 <LiveChannelCount>0</LiveChannelCount>
556 <LastModifiedTime>1700000000</LastModifiedTime>
557 <StandardStorage>512</StandardStorage>
558 <StandardObjectCount>5</StandardObjectCount>
559 <InfrequentAccessStorage>256</InfrequentAccessStorage>
560 <InfrequentAccessObjectCount>3</InfrequentAccessObjectCount>
561 <ArchiveStorage>256</ArchiveStorage>
562 <ArchiveObjectCount>2</ArchiveObjectCount>
563 <ColdArchiveStorage>0</ColdArchiveStorage>
564 <ColdArchiveObjectCount>0</ColdArchiveObjectCount>
565 <DeepColdArchiveStorage>0</DeepColdArchiveStorage>
566 <DeepColdArchiveObjectCount>0</DeepColdArchiveObjectCount>
567 </BucketStat>"#;
568
569 let result: GetBucketStatOutput = from_xml(xml).unwrap();
570 assert_eq!(result.storage, 1024);
571 assert_eq!(result.object_count, 10);
572 assert_eq!(result.standard_storage, 512);
573 }
574}