1use serde::{Deserialize, Serialize};
7use std::fmt;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ReplicationConfiguration {
14 #[serde(default)]
16 pub role: String,
17
18 pub rules: Vec<ReplicationRule>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct ReplicationRule {
26 pub id: String,
28
29 pub priority: i32,
31
32 pub status: ReplicationRuleStatus,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub prefix: Option<String>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub tags: Option<std::collections::HashMap<String, String>>,
42
43 pub destination: ReplicationDestination,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub delete_marker_replication: Option<bool>,
49
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub existing_object_replication: Option<bool>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub delete_replication: Option<bool>,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61pub enum ReplicationRuleStatus {
62 Enabled,
63 Disabled,
64}
65
66impl fmt::Display for ReplicationRuleStatus {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 ReplicationRuleStatus::Enabled => write!(f, "Enabled"),
70 ReplicationRuleStatus::Disabled => write!(f, "Disabled"),
71 }
72 }
73}
74
75impl std::str::FromStr for ReplicationRuleStatus {
76 type Err = String;
77
78 fn from_str(s: &str) -> Result<Self, Self::Err> {
79 match s.to_lowercase().as_str() {
80 "enabled" => Ok(ReplicationRuleStatus::Enabled),
81 "disabled" => Ok(ReplicationRuleStatus::Disabled),
82 _ => Err(format!("Invalid replication rule status: {s}")),
83 }
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct ReplicationDestination {
91 pub bucket_arn: String,
93
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub storage_class: Option<String>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize, Default)]
103pub struct BucketTarget {
104 #[serde(rename = "sourcebucket", default)]
105 pub source_bucket: String,
106
107 #[serde(default)]
108 pub endpoint: String,
109
110 #[serde(default)]
111 pub credentials: Option<BucketTargetCredentials>,
112
113 #[serde(rename = "targetbucket", default)]
114 pub target_bucket: String,
115
116 #[serde(default)]
117 pub secure: bool,
118
119 #[serde(default)]
120 pub path: String,
121
122 #[serde(default)]
123 pub api: String,
124
125 #[serde(default)]
126 pub arn: String,
127
128 #[serde(rename = "type", default)]
129 pub target_type: String,
130
131 #[serde(default)]
132 pub region: String,
133
134 #[serde(alias = "bandwidth", default)]
135 pub bandwidth_limit: i64,
136
137 #[serde(rename = "replicationSync", default)]
138 pub replication_sync: bool,
139
140 #[serde(default)]
141 pub storage_class: String,
142
143 #[serde(rename = "healthCheckDuration", default)]
144 pub health_check_duration: u64,
145
146 #[serde(rename = "disableProxy", default)]
147 pub disable_proxy: bool,
148
149 #[serde(rename = "isOnline", default)]
150 pub online: bool,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, Default)]
155pub struct BucketTargetCredentials {
156 #[serde(rename = "accessKey")]
157 pub access_key: String,
158 #[serde(rename = "secretKey")]
159 pub secret_key: String,
160}
161
162impl fmt::Display for ReplicationRule {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 write!(
165 f,
166 "{} (priority={}, {})",
167 self.id, self.priority, self.status
168 )
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_replication_rule_status_display() {
178 assert_eq!(ReplicationRuleStatus::Enabled.to_string(), "Enabled");
179 assert_eq!(ReplicationRuleStatus::Disabled.to_string(), "Disabled");
180 }
181
182 #[test]
183 fn test_replication_rule_status_from_str() {
184 assert_eq!(
185 "enabled".parse::<ReplicationRuleStatus>().unwrap(),
186 ReplicationRuleStatus::Enabled
187 );
188 assert!("invalid".parse::<ReplicationRuleStatus>().is_err());
189 }
190
191 #[test]
192 fn test_replication_configuration_serialization() {
193 let config = ReplicationConfiguration {
194 role: "arn:aws:iam::123456789:role/replication".to_string(),
195 rules: vec![ReplicationRule {
196 id: "rule-1".to_string(),
197 priority: 1,
198 status: ReplicationRuleStatus::Enabled,
199 prefix: Some("data/".to_string()),
200 tags: None,
201 destination: ReplicationDestination {
202 bucket_arn: "arn:aws:s3:::dest-bucket".to_string(),
203 storage_class: None,
204 },
205 delete_marker_replication: Some(true),
206 existing_object_replication: Some(true),
207 delete_replication: None,
208 }],
209 };
210
211 let json = serde_json::to_string_pretty(&config).unwrap();
212 let decoded: ReplicationConfiguration = serde_json::from_str(&json).unwrap();
213 assert_eq!(decoded.rules.len(), 1);
214 assert_eq!(decoded.rules[0].id, "rule-1");
215 assert_eq!(decoded.rules[0].priority, 1);
216 }
217
218 #[test]
219 fn test_bucket_target_serialization() {
220 let target = BucketTarget {
221 source_bucket: "my-bucket".to_string(),
222 endpoint: "http://remote:9000".to_string(),
223 credentials: Some(BucketTargetCredentials {
224 access_key: "admin".to_string(),
225 secret_key: "secret".to_string(),
226 }),
227 target_bucket: "dest-bucket".to_string(),
228 secure: false,
229 target_type: "replication".to_string(),
230 region: "us-east-1".to_string(),
231 replication_sync: true,
232 ..Default::default()
233 };
234
235 let json = serde_json::to_string(&target).unwrap();
236 assert!(json.contains("sourcebucket"));
237 assert!(json.contains("targetbucket"));
238 assert!(json.contains("replicationSync"));
239
240 let decoded: BucketTarget = serde_json::from_str(&json).unwrap();
241 assert_eq!(decoded.source_bucket, "my-bucket");
242 assert_eq!(decoded.target_bucket, "dest-bucket");
243 assert!(decoded.replication_sync);
244 }
245
246 #[test]
247 fn test_bucket_target_deserialization_from_backend() {
248 let json = r#"{"sourcebucket":"src","endpoint":"http://host:9000","credentials":{"accessKey":"ak","secretKey":"sk"},"targetbucket":"dst","secure":false,"path":"","api":"","arn":"arn:rustfs:replication::id:dst","type":"replication","region":"","bandwidth":0,"replicationSync":false,"storage_class":"","healthCheckDuration":0,"disableProxy":false,"isOnline":true}"#;
249 let target: BucketTarget = serde_json::from_str(json).unwrap();
250 assert_eq!(target.source_bucket, "src");
251 assert_eq!(target.target_bucket, "dst");
252 assert!(target.online);
253 assert_eq!(target.target_type, "replication");
254 }
255}