1use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
22#[serde(rename_all = "snake_case")]
23pub enum StoragePlatform {
24 Linux,
26 Windows,
28 S3,
30 Adls,
32 Gcs,
34 Hdfs,
36 JuiceFs,
38 MaprFs,
40 CephFs,
42 SeaweedFs,
44 Agnostic,
47}
48
49impl StoragePlatform {
50 #[must_use]
52 pub fn supports_atomic_move(&self) -> bool {
53 matches!(
54 self,
55 Self::Linux
56 | Self::Windows
57 | Self::Adls
58 | Self::Hdfs
59 | Self::JuiceFs
60 | Self::MaprFs
61 | Self::CephFs
62 )
63 }
64
65 #[must_use]
67 pub fn supports_native_notify(&self) -> bool {
68 matches!(
69 self,
70 Self::Linux
71 | Self::Windows
72 | Self::S3
73 | Self::Adls
74 | Self::Gcs
75 | Self::JuiceFs
76 | Self::MaprFs
77 | Self::CephFs
78 )
79 }
80
81 #[must_use]
83 pub fn supports_versioning(&self) -> bool {
84 matches!(self, Self::S3 | Self::Adls | Self::Gcs | Self::MaprFs | Self::CephFs)
85 }
86
87 #[must_use]
89 pub fn supports_lifecycle_rules(&self) -> bool {
90 matches!(self, Self::S3 | Self::Adls | Self::Gcs | Self::JuiceFs | Self::SeaweedFs)
91 }
92
93 #[must_use]
95 pub fn path_separator(&self) -> &'static str {
96 match self {
97 Self::Windows => "\\",
98 _ => "/",
99 }
100 }
101
102 #[must_use]
104 pub fn is_abstract(&self) -> bool {
105 matches!(self, Self::Agnostic)
106 }
107
108 #[must_use]
110 pub fn concrete_variants() -> &'static [StoragePlatform] {
111 &[
112 Self::Linux,
113 Self::Windows,
114 Self::S3,
115 Self::Adls,
116 Self::Gcs,
117 Self::Hdfs,
118 Self::JuiceFs,
119 Self::MaprFs,
120 Self::CephFs,
121 Self::SeaweedFs,
122 ]
123 }
124
125 #[must_use]
127 pub fn all_variants() -> &'static [StoragePlatform] {
128 &[
129 Self::Linux,
130 Self::Windows,
131 Self::S3,
132 Self::Adls,
133 Self::Gcs,
134 Self::Hdfs,
135 Self::JuiceFs,
136 Self::MaprFs,
137 Self::CephFs,
138 Self::SeaweedFs,
139 Self::Agnostic,
140 ]
141 }
142}
143
144impl std::fmt::Display for StoragePlatform {
145 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
146 match self {
147 Self::Linux => write!(f, "linux"),
148 Self::Windows => write!(f, "windows"),
149 Self::S3 => write!(f, "s3"),
150 Self::Adls => write!(f, "adls"),
151 Self::Gcs => write!(f, "gcs"),
152 Self::Hdfs => write!(f, "hdfs"),
153 Self::JuiceFs => write!(f, "juicefs"),
154 Self::MaprFs => write!(f, "maprfs"),
155 Self::CephFs => write!(f, "cephfs"),
156 Self::SeaweedFs => write!(f, "seaweedfs"),
157 Self::Agnostic => write!(f, "agnostic"),
158 }
159 }
160}
161
162impl std::str::FromStr for StoragePlatform {
163 type Err = String;
164
165 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
166 match s.to_lowercase().as_str() {
167 "linux" => Ok(Self::Linux),
168 "windows" => Ok(Self::Windows),
169 "s3" => Ok(Self::S3),
170 "adls" => Ok(Self::Adls),
171 "gcs" => Ok(Self::Gcs),
172 "hdfs" => Ok(Self::Hdfs),
173 "juicefs" => Ok(Self::JuiceFs),
174 "maprfs" => Ok(Self::MaprFs),
175 "cephfs" => Ok(Self::CephFs),
176 "seaweedfs" => Ok(Self::SeaweedFs),
177 "agnostic" => Ok(Self::Agnostic),
178 _ => Err(format!("Unknown storage platform: '{s}'")),
179 }
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn serde_json_round_trip_all_variants() {
189 let variants = vec![
190 (StoragePlatform::Linux, "\"linux\""),
191 (StoragePlatform::Windows, "\"windows\""),
192 (StoragePlatform::S3, "\"s3\""),
193 (StoragePlatform::Adls, "\"adls\""),
194 (StoragePlatform::Gcs, "\"gcs\""),
195 (StoragePlatform::Hdfs, "\"hdfs\""),
196 (StoragePlatform::JuiceFs, "\"juice_fs\""),
197 (StoragePlatform::MaprFs, "\"mapr_fs\""),
198 (StoragePlatform::CephFs, "\"ceph_fs\""),
199 (StoragePlatform::SeaweedFs, "\"seaweed_fs\""),
200 (StoragePlatform::Agnostic, "\"agnostic\""),
201 ];
202 for (variant, expected_json) in &variants {
203 let serialized = serde_json::to_string(variant).unwrap();
204 assert_eq!(&serialized, expected_json, "serialize {variant:?}");
205 let deserialized: StoragePlatform = serde_json::from_str(expected_json).unwrap();
206 assert_eq!(&deserialized, variant, "deserialize {expected_json}");
207 }
208 }
209
210 #[test]
211 fn serde_yaml_round_trip() {
212 for variant in StoragePlatform::all_variants() {
213 let yaml = serde_yaml::to_string(variant).unwrap();
214 let parsed: StoragePlatform = serde_yaml::from_str(&yaml).unwrap();
215 assert_eq!(parsed, *variant, "YAML round-trip failed for {variant:?}");
216 }
217 }
218
219 #[test]
220 fn capabilities_s3() {
221 let s3 = StoragePlatform::S3;
222 assert!(!s3.supports_atomic_move());
223 assert!(s3.supports_native_notify());
224 assert!(s3.supports_versioning());
225 assert!(s3.supports_lifecycle_rules());
226 assert_eq!(s3.path_separator(), "/");
227 assert!(!s3.is_abstract());
228 }
229
230 #[test]
231 fn capabilities_linux() {
232 let linux = StoragePlatform::Linux;
233 assert!(linux.supports_atomic_move());
234 assert!(linux.supports_native_notify());
235 assert!(!linux.supports_versioning());
236 assert!(!linux.supports_lifecycle_rules());
237 assert_eq!(linux.path_separator(), "/");
238 assert!(!linux.is_abstract());
239 }
240
241 #[test]
242 fn capabilities_windows() {
243 let win = StoragePlatform::Windows;
244 assert!(win.supports_atomic_move());
245 assert!(win.supports_native_notify());
246 assert!(!win.supports_versioning());
247 assert!(!win.supports_lifecycle_rules());
248 assert_eq!(win.path_separator(), "\\");
249 assert!(!win.is_abstract());
250 }
251
252 #[test]
253 fn capabilities_adls() {
254 let adls = StoragePlatform::Adls;
255 assert!(adls.supports_atomic_move());
256 assert!(adls.supports_native_notify());
257 assert!(adls.supports_versioning());
258 assert!(adls.supports_lifecycle_rules());
259 }
260
261 #[test]
262 fn capabilities_gcs() {
263 let gcs = StoragePlatform::Gcs;
264 assert!(!gcs.supports_atomic_move());
265 assert!(gcs.supports_native_notify());
266 assert!(gcs.supports_versioning());
267 assert!(gcs.supports_lifecycle_rules());
268 }
269
270 #[test]
271 fn capabilities_hdfs() {
272 let hdfs = StoragePlatform::Hdfs;
273 assert!(hdfs.supports_atomic_move());
274 assert!(!hdfs.supports_native_notify());
275 assert!(!hdfs.supports_versioning());
276 assert!(!hdfs.supports_lifecycle_rules());
277 }
278
279 #[test]
280 fn capabilities_juicefs() {
281 let jfs = StoragePlatform::JuiceFs;
282 assert!(jfs.supports_atomic_move());
283 assert!(jfs.supports_native_notify());
284 assert!(!jfs.supports_versioning());
285 assert!(jfs.supports_lifecycle_rules());
286 }
287
288 #[test]
289 fn capabilities_maprfs() {
290 let mapr = StoragePlatform::MaprFs;
291 assert!(mapr.supports_atomic_move());
292 assert!(mapr.supports_native_notify());
293 assert!(mapr.supports_versioning());
294 assert!(!mapr.supports_lifecycle_rules());
295 }
296
297 #[test]
298 fn capabilities_cephfs() {
299 let ceph = StoragePlatform::CephFs;
300 assert!(ceph.supports_atomic_move());
301 assert!(ceph.supports_native_notify());
302 assert!(ceph.supports_versioning());
303 assert!(!ceph.supports_lifecycle_rules());
304 }
305
306 #[test]
307 fn capabilities_seaweedfs() {
308 let sw = StoragePlatform::SeaweedFs;
309 assert!(!sw.supports_atomic_move());
310 assert!(!sw.supports_native_notify());
311 assert!(!sw.supports_versioning());
312 assert!(sw.supports_lifecycle_rules());
313 }
314
315 #[test]
316 fn agnostic_is_abstract() {
317 assert!(StoragePlatform::Agnostic.is_abstract());
318 for variant in StoragePlatform::concrete_variants() {
319 assert!(!variant.is_abstract(), "{variant:?} should not be abstract");
320 }
321 }
322
323 #[test]
324 fn display_all_variants() {
325 assert_eq!(StoragePlatform::Linux.to_string(), "linux");
326 assert_eq!(StoragePlatform::Windows.to_string(), "windows");
327 assert_eq!(StoragePlatform::S3.to_string(), "s3");
328 assert_eq!(StoragePlatform::Adls.to_string(), "adls");
329 assert_eq!(StoragePlatform::Gcs.to_string(), "gcs");
330 assert_eq!(StoragePlatform::Hdfs.to_string(), "hdfs");
331 assert_eq!(StoragePlatform::JuiceFs.to_string(), "juicefs");
332 assert_eq!(StoragePlatform::MaprFs.to_string(), "maprfs");
333 assert_eq!(StoragePlatform::CephFs.to_string(), "cephfs");
334 assert_eq!(StoragePlatform::SeaweedFs.to_string(), "seaweedfs");
335 assert_eq!(StoragePlatform::Agnostic.to_string(), "agnostic");
336 }
337
338 #[test]
339 fn from_str_all_variants() {
340 assert_eq!("linux".parse::<StoragePlatform>().unwrap(), StoragePlatform::Linux);
341 assert_eq!("S3".parse::<StoragePlatform>().unwrap(), StoragePlatform::S3);
342 assert_eq!("ADLS".parse::<StoragePlatform>().unwrap(), StoragePlatform::Adls);
343 assert_eq!("GCS".parse::<StoragePlatform>().unwrap(), StoragePlatform::Gcs);
344 assert_eq!("HdFs".parse::<StoragePlatform>().unwrap(), StoragePlatform::Hdfs);
345 assert_eq!("agnostic".parse::<StoragePlatform>().unwrap(), StoragePlatform::Agnostic);
346 }
347
348 #[test]
349 fn from_str_unknown_returns_error() {
350 let result = "unknown".parse::<StoragePlatform>();
351 assert!(result.is_err());
352 assert!(result.unwrap_err().contains("Unknown storage platform"));
353 }
354
355 #[test]
356 fn all_variants_has_correct_count() {
357 assert_eq!(StoragePlatform::all_variants().len(), 11);
358 }
359
360 #[test]
361 fn concrete_variants_excludes_agnostic() {
362 let concrete = StoragePlatform::concrete_variants();
363 assert_eq!(concrete.len(), 10);
364 assert!(!concrete.contains(&StoragePlatform::Agnostic));
365 }
366
367 #[test]
368 fn all_variants_have_non_empty_path_separator() {
369 for variant in StoragePlatform::all_variants() {
370 let sep = variant.path_separator();
371 assert!(!sep.is_empty(), "{variant:?} has empty separator");
372 }
373 }
374
375 #[test]
376 fn display_round_trips_through_from_str() {
377 for variant in StoragePlatform::all_variants() {
378 let display = variant.to_string();
379 let parsed: StoragePlatform = display.parse().unwrap();
380 assert_eq!(parsed, *variant, "Display → FromStr round-trip failed for {variant:?}");
381 }
382 }
383}