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