xt_oss/oss/entities/
lifecycle.rs

1use super::{tag::Tag, Status, StorageClass};
2use serde::{Deserialize, Serialize};
3
4pub mod builder {
5    use crate::oss::entities::{Status, StorageClass};
6
7    use super::{
8        AbortMultipartUpload, Expiration, Filter, LifecycleConfiguration,
9        NoncurrentVersionExpiration, Rule, Transition,
10    };
11
12    #[derive(Default, Clone, Debug)]
13    pub struct FilterBuilder {}
14
15    impl FilterBuilder {
16        pub fn new() -> Self {
17            Self::default()
18        }
19
20        pub fn build() -> Filter {
21            Filter::default()
22        }
23    }
24
25    #[derive(Default, Clone, Debug)]
26    pub struct ExpirationBuilder {
27        days: Option<i32>,
28        created_before_date: Option<String>,
29        expired_object_delete_marker: Option<bool>,
30    }
31
32    impl ExpirationBuilder {
33        pub fn new() -> Self {
34            Self::default()
35        }
36
37        pub fn with_days(mut self, value: i32) -> Self {
38            self.days = Some(value);
39            self
40        }
41
42        pub fn with_created_before_date(mut self, value: String) -> Self {
43            self.created_before_date = Some(value);
44            self
45        }
46
47        pub fn with_expired_object_delete_marker(mut self, value: bool) -> Self {
48            self.expired_object_delete_marker = Some(value);
49            self
50        }
51
52        pub fn build(&self) -> Expiration {
53            Expiration {
54                days: self.days,
55                created_before_date: self.created_before_date.clone(),
56                expired_object_delete_marker: self.expired_object_delete_marker,
57            }
58        }
59    }
60
61    #[derive(Default, Debug, Clone)]
62    pub struct TransitionBuilder {
63        days: Option<i32>,
64        storage_class: StorageClass,
65        is_access_time: Option<bool>,
66        return_to_std_when_visit: Option<bool>,
67        allow_small_file: Option<bool>,
68    }
69
70    impl TransitionBuilder {
71        pub fn new() -> Self {
72            Self::default()
73        }
74
75        pub fn with_days(mut self, value: i32) -> Self {
76            self.days = Some(value);
77            self
78        }
79
80        pub fn with_torage_class(mut self, value: StorageClass) -> Self {
81            self.storage_class = value;
82            self
83        }
84
85        pub fn with_is_access_time(mut self, value: bool) -> Self {
86            self.is_access_time = Some(value);
87            self
88        }
89
90        pub fn with_return_to_std_when_visit(mut self, value: bool) -> Self {
91            self.return_to_std_when_visit = Some(value);
92            self
93        }
94
95        pub fn with_allow_small_file(mut self, value: bool) -> Self {
96            self.allow_small_file = Some(value);
97            self
98        }
99
100        pub fn build(&self) -> Transition {
101            Transition {
102                days: self.days,
103                storage_class: self.storage_class.clone(),
104                is_access_time: self.is_access_time,
105                return_to_std_when_visit: self.return_to_std_when_visit,
106                allow_small_file: self.allow_small_file,
107            }
108        }
109    }
110
111    #[derive(Default, Debug, Clone)]
112    pub struct RuleBuilder<'a> {
113        id: &'a str,
114        prefix: &'a str,
115        status: Status,
116        transition: Option<Vec<Transition>>,
117        filter: Option<Filter>,
118        expiration: Option<Expiration>,
119        noncurrent_version_expiration: Option<NoncurrentVersionExpiration>,
120        abort_multipart_upload: Option<AbortMultipartUpload>,
121    }
122
123    impl<'a> RuleBuilder<'a> {
124        pub fn new() -> Self {
125            Self::default()
126        }
127
128        pub fn with_id(mut self, value: &'a str) -> Self {
129            self.id = value;
130            self
131        }
132
133        pub fn with_prefix(mut self, value: &'a str) -> Self {
134            self.prefix = value;
135            self
136        }
137
138        pub fn with_status(mut self, value: Status) -> Self {
139            self.status = value;
140            self
141        }
142
143        pub fn with_transition(mut self, value: Transition) -> Self {
144            let transitions = if let Some(mut transitions) = self.transition {
145                transitions.push(value);
146                transitions
147            } else {
148                vec![value]
149            };
150            self.transition = Some(transitions);
151            self
152        }
153
154        pub fn with_filter(mut self, value: Filter) -> Self {
155            self.filter = Some(value);
156            self
157        }
158
159        pub fn with_expiration(mut self, value: Expiration) -> Self {
160            self.expiration = Some(value);
161            self
162        }
163
164        pub fn with_noncurrent_version_expiration(mut self, days: i32) -> Self {
165            self.noncurrent_version_expiration = Some(NoncurrentVersionExpiration {
166                noncurrent_days: days,
167            });
168            self
169        }
170
171        pub fn with_abort_multipart_upload(mut self, days: i32) -> Self {
172            self.abort_multipart_upload = Some(AbortMultipartUpload { days });
173            self
174        }
175
176        pub fn build(&self) -> Rule {
177            Rule {
178                // id: self.id.to_string(),
179                id: self.id.into(),
180                prefix: self.prefix.into(),
181                status: self.status.clone(),
182                transition: self.transition.clone(),
183                filter: self.filter.clone(),
184                expiration: self.expiration.clone(),
185                noncurrent_version_expiration: self.noncurrent_version_expiration.clone(),
186                abort_multipart_upload: self.abort_multipart_upload.clone(),
187            }
188        }
189    }
190
191    #[derive(Default, Debug, Clone)]
192    pub struct LifecycleConfigurationBuilder {
193        rules: Vec<Rule>,
194    }
195
196    impl LifecycleConfigurationBuilder {
197        pub fn new() -> Self {
198            Self::default()
199        }
200
201        pub fn with_rule(mut self, value: Rule) -> Self {
202            self.rules.push(value);
203            self
204        }
205
206        pub fn build(&self) -> LifecycleConfiguration {
207            LifecycleConfiguration {
208                rule: self.rules.clone(),
209            }
210        }
211    }
212}
213
214#[derive(Debug, Serialize, Deserialize, Clone, Default)]
215pub struct Not {
216    #[serde(rename = "Prefix")]
217    pub prefix: String,
218    #[serde(rename = "Tag")]
219    pub tag: Tag,
220}
221
222#[derive(Debug, Serialize, Deserialize, Clone, Default)]
223pub struct Filter {
224    #[serde(rename = "Not", skip_serializing_if = "Option::is_none")]
225    pub not: Option<Not>,
226    #[serde(
227        rename = "ObjectSizeGreaterThan",
228        skip_serializing_if = "Option::is_none"
229    )]
230    pub object_size_greater_than: Option<i32>,
231    #[serde(rename = "ObjectSizeLessThan", skip_serializing_if = "Option::is_none")]
232    pub object_size_less_than: Option<i32>,
233}
234
235#[derive(Debug, Serialize, Deserialize, Clone, Default)]
236pub struct AbortMultipartUpload {
237    #[serde(rename = "Days")]
238    pub days: i32,
239}
240
241#[derive(Debug, Serialize, Deserialize, Clone, Default)]
242pub struct NoncurrentVersionTransition {
243    #[serde(rename = "NoncurrentDays", skip_serializing_if = "Option::is_none")]
244    pub noncurrent_days: Option<bool>,
245    #[serde(rename = "StorageClass")]
246    pub storage_class: StorageClass,
247}
248
249#[derive(Debug, Serialize, Deserialize, Default, Clone)]
250pub struct Transition {
251    #[serde(rename = "Days")]
252    pub days: Option<i32>,
253    #[serde(rename = "StorageClass")]
254    pub storage_class: StorageClass,
255    #[serde(rename = "IsAccessTime", skip_serializing_if = "Option::is_none")]
256    pub is_access_time: Option<bool>,
257    #[serde(
258        rename = "ReturnToStdWhenVisit",
259        skip_serializing_if = "Option::is_none"
260    )]
261    pub return_to_std_when_visit: Option<bool>,
262    #[serde(rename = "AllowSmallFile", skip_serializing_if = "Option::is_none")]
263    pub allow_small_file: Option<bool>,
264}
265
266#[derive(Debug, Serialize, Deserialize, Default, Clone)]
267pub struct Expiration {
268    #[serde(rename = "Days", skip_serializing_if = "Option::is_none")]
269    pub days: Option<i32>,
270    #[serde(rename = "CreatedBeforeDate", skip_serializing_if = "Option::is_none")]
271    pub created_before_date: Option<String>,
272    #[serde(
273        rename = "ExpiredObjectDeleteMarker",
274        skip_serializing_if = "Option::is_none"
275    )]
276    pub expired_object_delete_marker: Option<bool>,
277}
278
279#[derive(Debug, Serialize, Deserialize, Default, Clone)]
280pub struct NoncurrentVersionExpiration {
281    #[serde(rename = "NoncurrentDays")]
282    pub noncurrent_days: i32,
283}
284
285#[derive(Debug, Serialize, Deserialize, Default, Clone)]
286pub struct Rule {
287    #[serde(rename = "ID")]
288    pub id: String,
289    #[serde(rename = "Prefix")]
290    pub prefix: String,
291    #[serde(rename = "Status")]
292    pub status: Status,
293    #[serde(rename = "Transition", skip_serializing_if = "Option::is_none")]
294    pub transition: Option<Vec<Transition>>,
295    #[serde(rename = "Filter", skip_serializing_if = "Option::is_none")]
296    pub filter: Option<Filter>,
297    #[serde(rename = "Expiration", skip_serializing_if = "Option::is_none")]
298    pub expiration: Option<Expiration>,
299    #[serde(
300        rename = "NoncurrentVersionExpiration",
301        skip_serializing_if = "Option::is_none"
302    )]
303    pub noncurrent_version_expiration: Option<NoncurrentVersionExpiration>,
304    #[serde(
305        rename = "AbortMultipartUpload",
306        skip_serializing_if = "Option::is_none"
307    )]
308    pub abort_multipart_upload: Option<AbortMultipartUpload>,
309}
310
311#[derive(Debug, Serialize, Deserialize, Default, Clone)]
312pub struct LifecycleConfiguration {
313    #[serde(rename = "Rule")]
314    pub rule: Vec<Rule>,
315}
316
317#[cfg(test)]
318mod tests {
319    use crate::oss::entities::Status;
320
321    use super::builder::*;
322    use super::*;
323
324    #[test]
325    /// 返回内容解析测试
326    fn lifecycle_configuration_return_parse_1() {
327        let xml_content = r#"<?xml version="1.0" encoding="UTF-8"?>
328<LifecycleConfiguration>
329  <Rule>
330    <ID>delete after one day</ID>
331    <Prefix>logs1/</Prefix>
332    <Status>Enabled</Status>
333    <Expiration>
334      <Days>1</Days>
335    </Expiration>
336  </Rule>
337  <Rule>
338  <ID>mtime transition1</ID>
339  <Prefix>logs2/</Prefix>
340  <Status>Enabled</Status>
341  <Transition>
342     <Days>30</Days>
343     <StorageClass>IA</StorageClass>
344  </Transition>
345 </Rule>
346 <Rule>
347   <ID>mtime transition2</ID>
348   <Prefix>logs3/</Prefix>
349   <Status>Enabled</Status>
350   <Transition>
351     <Days>30</Days>
352     <StorageClass>IA</StorageClass>
353     <IsAccessTime>false</IsAccessTime>
354  </Transition>
355 </Rule>
356</LifecycleConfiguration>"#;
357        let object: LifecycleConfiguration = quick_xml::de::from_str(xml_content).unwrap();
358
359        // let json_str = serde_json::to_string_pretty(&object).unwrap();
360        // println!("{}", json_str);
361        let left = "mtime transition1";
362        let right = &object.rule[1].id;
363        assert_eq!(left, right);
364    }
365
366    #[test]
367    // 返回结果分析测试
368    fn lifecycle_configuration_return_parse_2() {
369        let xml_content = r#"<?xml version="1.0" encoding="UTF-8"?>
370<LifecycleConfiguration>
371  <Rule>
372    <ID>atime transition1</ID>
373    <Prefix>logs1/</Prefix>
374    <Status>Enabled</Status>
375      <Transition>
376         <Days>30</Days>
377         <StorageClass>IA</StorageClass>
378         <IsAccessTime>true</IsAccessTime>
379         <ReturnToStdWhenVisit>false</ReturnToStdWhenVisit>
380      </Transition>
381    <AtimeBase>1631698332</AtimeBase>
382   </Rule>
383   <Rule>
384      <ID>atime transition2</ID>
385      <Prefix>logs2/</Prefix>
386      <Status>Enabled</Status>
387      <NoncurrentVersionTransition>
388         <NoncurrentDays>10</NoncurrentDays>
389         <StorageClass>IA</StorageClass>
390         <IsAccessTime>true</IsAccessTime>
391         <ReturnToStdWhenVisit>false</ReturnToStdWhenVisit>
392      </NoncurrentVersionTransition>
393     <AtimeBase>1631698332</AtimeBase>
394  </Rule>
395</LifecycleConfiguration>"#;
396        let object: LifecycleConfiguration = quick_xml::de::from_str(xml_content).unwrap();
397
398        let left = "atime transition2";
399        let right = &object.rule[1].id;
400        assert_eq!(left, right);
401    }
402
403    // xml转换
404    #[test]
405    // 示例1:基于最后一次修改时间策略仅执行转换文件存储类型操作
406    fn lifecycle_configuration_builder_1() {
407        let config = LifecycleConfigurationBuilder::new()
408            .with_rule(
409                RuleBuilder::new()
410                    .with_id("rule")
411                    .with_prefix("log")
412                    .with_status(Status::Enabled)
413                    .with_transition(
414                        TransitionBuilder::new()
415                            .with_days(30)
416                            .with_torage_class(StorageClass::IA)
417                            .build(),
418                    )
419                    .build(),
420            )
421            .build();
422
423        let left = "<LifecycleConfiguration><Rule><ID>rule</ID><Prefix>log</Prefix><Status>Enabled</Status><Transition><Days>30</Days><StorageClass>IA</StorageClass></Transition></Rule></LifecycleConfiguration>";
424        let right = quick_xml::se::to_string(&config).unwrap();
425
426        assert_eq!(left, right);
427    }
428
429    // xml转换
430    #[test]
431    // 示例2:基于最后一次修改时间策略仅执行删除文件操作
432    fn lifecycle_configuration_builder_2() {
433        let config = LifecycleConfigurationBuilder::new()
434            .with_rule(
435                RuleBuilder::new()
436                    .with_id("rule")
437                    .with_prefix("log")
438                    .with_status(Status::Enabled)
439                    .with_expiration(ExpirationBuilder::new().with_days(90).build())
440                    .build(),
441            )
442            .build();
443
444        let left = "<LifecycleConfiguration><Rule><ID>rule</ID><Prefix>log</Prefix><Status>Enabled</Status><Expiration><Days>90</Days></Expiration></Rule></LifecycleConfiguration>";
445        let right = quick_xml::se::to_string(&config).unwrap();
446
447        assert_eq!(left, right);
448    }
449
450    // xml转换
451    #[test]
452    // 示例3:基于最后一次修改时间执行转换文件存储类型以及删除操作
453    fn lifecycle_configuration_builder_3() {
454        let config = LifecycleConfigurationBuilder::new()
455            .with_rule(
456                RuleBuilder::new()
457                    .with_id("rule")
458                    .with_prefix("log")
459                    .with_status(Status::Enabled)
460                    .with_transition(
461                        TransitionBuilder::new()
462                            .with_days(30)
463                            .with_torage_class(StorageClass::IA)
464                            .build(),
465                    )
466                    .with_transition(
467                        TransitionBuilder::new()
468                            .with_days(60)
469                            .with_torage_class(StorageClass::Archive)
470                            .build(),
471                    )
472                    .with_expiration(ExpirationBuilder::new().with_days(60).build())
473                    .build(),
474            )
475            .build();
476        let left = "<LifecycleConfiguration><Rule><ID>rule</ID><Prefix>log</Prefix><Status>Enabled</Status><Transition><Days>30</Days><StorageClass>IA</StorageClass></Transition><Transition><Days>60</Days><StorageClass>Archive</StorageClass></Transition><Expiration><Days>60</Days></Expiration></Rule></LifecycleConfiguration>";
477        let right = quick_xml::se::to_string(&config).unwrap();
478
479        assert_eq!(left, right);
480    }
481
482    #[test]
483    // 示例4:基于最后一次修改时间执行删除历史版本文件及清理删除标记的操作
484    fn lifecycle_configuration_builder_4() {
485        let config = LifecycleConfigurationBuilder::new()
486            .with_rule(
487                RuleBuilder::new()
488                    .with_id("rule")
489                    .with_prefix("")
490                    .with_status(Status::Enabled)
491                    .with_expiration(
492                        ExpirationBuilder::new()
493                            .with_expired_object_delete_marker(true)
494                            .build(),
495                    )
496                    .with_noncurrent_version_expiration(5)
497                    .build(),
498            )
499            .build();
500
501        let left = "<LifecycleConfiguration><Rule><ID>rule</ID><Prefix/><Status>Enabled</Status><Expiration><ExpiredObjectDeleteMarker>true</ExpiredObjectDeleteMarker></Expiration><NoncurrentVersionExpiration><NoncurrentDays>5</NoncurrentDays></NoncurrentVersionExpiration></Rule></LifecycleConfiguration>";
502        let right = quick_xml::se::to_string(&config).unwrap();
503
504        assert_eq!(left, right);
505    }
506
507    #[test]
508    // 示例5:基于最后一次修改时间策略限制除指定前缀、标签以外的文件执行转换存储类型及删除操作
509    fn lifecycle_configuration_builder_5() {
510        let config = LifecycleConfigurationBuilder::new()
511            .with_rule(
512                RuleBuilder::new()
513                    .with_id("rule")
514                    .with_prefix("log")
515                    .with_status(Status::Enabled)
516                    .with_transition(
517                        TransitionBuilder::new()
518                            .with_days(30)
519                            .with_torage_class(StorageClass::IA)
520                            .with_is_access_time(true)
521                            .with_return_to_std_when_visit(true)
522                            .build(),
523                    )
524                    .build(),
525            )
526            .build();
527
528        let left = "<LifecycleConfiguration><Rule><ID>rule</ID><Prefix>log</Prefix><Status>Enabled</Status><Transition><Days>30</Days><StorageClass>IA</StorageClass><IsAccessTime>true</IsAccessTime><ReturnToStdWhenVisit>true</ReturnToStdWhenVisit></Transition></Rule></LifecycleConfiguration>";
529        let right = quick_xml::se::to_string(&config).unwrap();
530
531        assert_eq!(left, right);
532    }
533    #[test]
534    // 示例6:基于最后一次访问时间策略转换文件存储类型
535    fn lifecycle_configuration_builder_6() {
536        let config = LifecycleConfigurationBuilder::new()
537            .with_rule(
538                RuleBuilder::new()
539                    .with_id("rule")
540                    .with_prefix("/")
541                    .with_status(Status::Enabled)
542                    .with_abort_multipart_upload(30)
543                    .build(),
544            )
545            .build();
546
547        let left = "<LifecycleConfiguration><Rule><ID>rule</ID><Prefix>/</Prefix><Status>Enabled</Status><AbortMultipartUpload><Days>30</Days></AbortMultipartUpload></Rule></LifecycleConfiguration>";
548        let right = quick_xml::se::to_string(&config).unwrap();
549
550        assert_eq!(left, right);
551    }
552
553    #[test]
554    // 示例7:基于最后一次修改时间执行删除碎片操作
555    fn lifecycle_configuration_builder_7() {
556        let config = LifecycleConfigurationBuilder::new()
557            .with_rule(
558                RuleBuilder::new()
559                    .with_id("rule")
560                    .with_prefix("/")
561                    .with_status(Status::Enabled)
562                    .with_abort_multipart_upload(30)
563                    .build(),
564            )
565            .build();
566        let left = "<LifecycleConfiguration><Rule><ID>rule</ID><Prefix>/</Prefix><Status>Enabled</Status><AbortMultipartUpload><Days>30</Days></AbortMultipartUpload></Rule></LifecycleConfiguration>";
567        let right = quick_xml::se::to_string(&config).unwrap();
568        assert_eq!(left, right);
569    }
570
571    #[test]
572    // 示例8:基于最后一次修改时间对重叠前缀的Object执行删除操作
573    fn lifecycle_configuration_builder_8() {
574        let config = LifecycleConfigurationBuilder::new()
575            .with_rule(
576                RuleBuilder::new()
577                    .with_id("Rule1")
578                    .with_prefix("dir1")
579                    .with_expiration(ExpirationBuilder::new().with_days(180).build())
580                    .build(),
581            )
582            .with_rule(
583                RuleBuilder::new()
584                    .with_id("Rule2")
585                    .with_prefix("dir1/dir2")
586                    .with_status(Status::Enabled)
587                    .with_expiration(ExpirationBuilder::new().with_days(30).build())
588                    .build(),
589            )
590            .build();
591
592        let right = quick_xml::se::to_string(&config).unwrap();
593        let left = r#"<LifecycleConfiguration><Rule><ID>Rule1</ID><Prefix>dir1</Prefix><Status>Disabled</Status><Expiration><Days>180</Days></Expiration></Rule><Rule><ID>Rule2</ID><Prefix>dir1/dir2</Prefix><Status>Enabled</Status><Expiration><Days>30</Days></Expiration></Rule></LifecycleConfiguration>"#;
594
595        assert_eq!(left, right);
596    }
597}