1use std::collections::HashMap;
7use std::fmt;
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct LifecycleConfiguration {
14 pub rules: Vec<LifecycleRule>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase")]
21pub struct LifecycleRule {
22 pub id: String,
24
25 pub status: LifecycleRuleStatus,
27
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub prefix: Option<String>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub tags: Option<HashMap<String, String>>,
35
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub expiration: Option<LifecycleExpiration>,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub transition: Option<LifecycleTransition>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub noncurrent_version_expiration: Option<NoncurrentVersionExpiration>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub noncurrent_version_transition: Option<NoncurrentVersionTransition>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub abort_incomplete_multipart_upload_days: Option<i32>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub expired_object_delete_marker: Option<bool>,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
63pub enum LifecycleRuleStatus {
64 Enabled,
65 Disabled,
66}
67
68impl fmt::Display for LifecycleRuleStatus {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 LifecycleRuleStatus::Enabled => write!(f, "Enabled"),
72 LifecycleRuleStatus::Disabled => write!(f, "Disabled"),
73 }
74 }
75}
76
77impl std::str::FromStr for LifecycleRuleStatus {
78 type Err = String;
79
80 fn from_str(s: &str) -> Result<Self, Self::Err> {
81 match s.to_lowercase().as_str() {
82 "enabled" => Ok(LifecycleRuleStatus::Enabled),
83 "disabled" => Ok(LifecycleRuleStatus::Disabled),
84 _ => Err(format!("Invalid lifecycle rule status: {s}")),
85 }
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct LifecycleExpiration {
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub days: Option<i32>,
95
96 #[serde(skip_serializing_if = "Option::is_none")]
98 pub date: Option<String>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103#[serde(rename_all = "camelCase")]
104pub struct LifecycleTransition {
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub days: Option<i32>,
108
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub date: Option<String>,
112
113 pub storage_class: String,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119#[serde(rename_all = "camelCase")]
120pub struct NoncurrentVersionExpiration {
121 pub noncurrent_days: i32,
123
124 #[serde(skip_serializing_if = "Option::is_none")]
126 pub newer_noncurrent_versions: Option<i32>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct NoncurrentVersionTransition {
133 pub noncurrent_days: i32,
135
136 pub storage_class: String,
138}
139
140impl fmt::Display for LifecycleRule {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 write!(f, "{} ({})", self.id, self.status)
143 }
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149
150 #[test]
151 fn test_lifecycle_rule_status_display() {
152 assert_eq!(LifecycleRuleStatus::Enabled.to_string(), "Enabled");
153 assert_eq!(LifecycleRuleStatus::Disabled.to_string(), "Disabled");
154 }
155
156 #[test]
157 fn test_lifecycle_rule_status_from_str() {
158 assert_eq!(
159 "enabled".parse::<LifecycleRuleStatus>().unwrap(),
160 LifecycleRuleStatus::Enabled
161 );
162 assert_eq!(
163 "Disabled".parse::<LifecycleRuleStatus>().unwrap(),
164 LifecycleRuleStatus::Disabled
165 );
166 assert!("invalid".parse::<LifecycleRuleStatus>().is_err());
167 }
168
169 #[test]
170 fn test_lifecycle_rule_serialization() {
171 let rule = LifecycleRule {
172 id: "rule-1".to_string(),
173 status: LifecycleRuleStatus::Enabled,
174 prefix: Some("logs/".to_string()),
175 tags: None,
176 expiration: Some(LifecycleExpiration {
177 days: Some(30),
178 date: None,
179 }),
180 transition: None,
181 noncurrent_version_expiration: None,
182 noncurrent_version_transition: None,
183 abort_incomplete_multipart_upload_days: Some(7),
184 expired_object_delete_marker: None,
185 };
186
187 let json = serde_json::to_string(&rule).unwrap();
188 let decoded: LifecycleRule = serde_json::from_str(&json).unwrap();
189 assert_eq!(decoded.id, "rule-1");
190 assert_eq!(decoded.status, LifecycleRuleStatus::Enabled);
191 assert_eq!(decoded.prefix.as_deref(), Some("logs/"));
192 assert_eq!(decoded.expiration.as_ref().unwrap().days, Some(30));
193 assert_eq!(decoded.abort_incomplete_multipart_upload_days, Some(7));
194 }
195
196 #[test]
197 fn test_lifecycle_transition_serialization() {
198 let transition = LifecycleTransition {
199 days: Some(90),
200 date: None,
201 storage_class: "WARM_TIER".to_string(),
202 };
203
204 let json = serde_json::to_string(&transition).unwrap();
205 assert!(json.contains("storageClass"));
206 assert!(json.contains("WARM_TIER"));
207
208 let decoded: LifecycleTransition = serde_json::from_str(&json).unwrap();
209 assert_eq!(decoded.storage_class, "WARM_TIER");
210 }
211
212 #[test]
213 fn test_lifecycle_configuration_serialization() {
214 let config = LifecycleConfiguration {
215 rules: vec![LifecycleRule {
216 id: "expire-old".to_string(),
217 status: LifecycleRuleStatus::Enabled,
218 prefix: None,
219 tags: None,
220 expiration: Some(LifecycleExpiration {
221 days: Some(365),
222 date: None,
223 }),
224 transition: None,
225 noncurrent_version_expiration: Some(NoncurrentVersionExpiration {
226 noncurrent_days: 30,
227 newer_noncurrent_versions: Some(3),
228 }),
229 noncurrent_version_transition: None,
230 abort_incomplete_multipart_upload_days: None,
231 expired_object_delete_marker: Some(true),
232 }],
233 };
234
235 let json = serde_json::to_string_pretty(&config).unwrap();
236 let decoded: LifecycleConfiguration = serde_json::from_str(&json).unwrap();
237 assert_eq!(decoded.rules.len(), 1);
238 assert_eq!(decoded.rules[0].id, "expire-old");
239 assert_eq!(
240 decoded.rules[0]
241 .noncurrent_version_expiration
242 .as_ref()
243 .unwrap()
244 .newer_noncurrent_versions,
245 Some(3)
246 );
247 }
248
249 #[test]
250 fn test_noncurrent_version_transition_serialization() {
251 let nvt = NoncurrentVersionTransition {
252 noncurrent_days: 60,
253 storage_class: "COLD_TIER".to_string(),
254 };
255
256 let json = serde_json::to_string(&nvt).unwrap();
257 let decoded: NoncurrentVersionTransition = serde_json::from_str(&json).unwrap();
258 assert_eq!(decoded.noncurrent_days, 60);
259 assert_eq!(decoded.storage_class, "COLD_TIER");
260 }
261}