use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum StoragePlatform {
Linux,
Windows,
S3,
Adls,
Gcs,
Hdfs,
JuiceFs,
MaprFs,
CephFs,
SeaweedFs,
Agnostic,
}
impl StoragePlatform {
#[must_use]
pub fn supports_atomic_move(&self) -> bool {
matches!(
self,
Self::Linux
| Self::Windows
| Self::Adls
| Self::Hdfs
| Self::JuiceFs
| Self::MaprFs
| Self::CephFs
)
}
#[must_use]
pub fn supports_native_notify(&self) -> bool {
matches!(
self,
Self::Linux
| Self::Windows
| Self::S3
| Self::Adls
| Self::Gcs
| Self::JuiceFs
| Self::MaprFs
| Self::CephFs
)
}
#[must_use]
pub fn supports_versioning(&self) -> bool {
matches!(self, Self::S3 | Self::Adls | Self::Gcs | Self::MaprFs | Self::CephFs)
}
#[must_use]
pub fn supports_lifecycle_rules(&self) -> bool {
matches!(self, Self::S3 | Self::Adls | Self::Gcs | Self::JuiceFs | Self::SeaweedFs)
}
#[must_use]
pub fn path_separator(&self) -> &'static str {
match self {
Self::Windows => "\\",
_ => "/",
}
}
#[must_use]
pub fn is_abstract(&self) -> bool {
matches!(self, Self::Agnostic)
}
#[must_use]
pub fn concrete_variants() -> &'static [StoragePlatform] {
&[
Self::Linux,
Self::Windows,
Self::S3,
Self::Adls,
Self::Gcs,
Self::Hdfs,
Self::JuiceFs,
Self::MaprFs,
Self::CephFs,
Self::SeaweedFs,
]
}
#[must_use]
pub fn all_variants() -> &'static [StoragePlatform] {
&[
Self::Linux,
Self::Windows,
Self::S3,
Self::Adls,
Self::Gcs,
Self::Hdfs,
Self::JuiceFs,
Self::MaprFs,
Self::CephFs,
Self::SeaweedFs,
Self::Agnostic,
]
}
}
impl std::fmt::Display for StoragePlatform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Linux => write!(f, "linux"),
Self::Windows => write!(f, "windows"),
Self::S3 => write!(f, "s3"),
Self::Adls => write!(f, "adls"),
Self::Gcs => write!(f, "gcs"),
Self::Hdfs => write!(f, "hdfs"),
Self::JuiceFs => write!(f, "juicefs"),
Self::MaprFs => write!(f, "maprfs"),
Self::CephFs => write!(f, "cephfs"),
Self::SeaweedFs => write!(f, "seaweedfs"),
Self::Agnostic => write!(f, "agnostic"),
}
}
}
impl std::str::FromStr for StoragePlatform {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"linux" => Ok(Self::Linux),
"windows" => Ok(Self::Windows),
"s3" => Ok(Self::S3),
"adls" => Ok(Self::Adls),
"gcs" => Ok(Self::Gcs),
"hdfs" => Ok(Self::Hdfs),
"juicefs" => Ok(Self::JuiceFs),
"maprfs" => Ok(Self::MaprFs),
"cephfs" => Ok(Self::CephFs),
"seaweedfs" => Ok(Self::SeaweedFs),
"agnostic" => Ok(Self::Agnostic),
_ => Err(format!("Unknown storage platform: '{s}'")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde_json_round_trip_all_variants() {
let variants = vec![
(StoragePlatform::Linux, "\"linux\""),
(StoragePlatform::Windows, "\"windows\""),
(StoragePlatform::S3, "\"s3\""),
(StoragePlatform::Adls, "\"adls\""),
(StoragePlatform::Gcs, "\"gcs\""),
(StoragePlatform::Hdfs, "\"hdfs\""),
(StoragePlatform::JuiceFs, "\"juice_fs\""),
(StoragePlatform::MaprFs, "\"mapr_fs\""),
(StoragePlatform::CephFs, "\"ceph_fs\""),
(StoragePlatform::SeaweedFs, "\"seaweed_fs\""),
(StoragePlatform::Agnostic, "\"agnostic\""),
];
for (variant, expected_json) in &variants {
let serialized = serde_json::to_string(variant).unwrap();
assert_eq!(&serialized, expected_json, "serialize {variant:?}");
let deserialized: StoragePlatform = serde_json::from_str(expected_json).unwrap();
assert_eq!(&deserialized, variant, "deserialize {expected_json}");
}
}
#[test]
fn serde_yaml_round_trip() {
for variant in StoragePlatform::all_variants() {
let yaml = serde_yaml::to_string(variant).unwrap();
let parsed: StoragePlatform = serde_yaml::from_str(&yaml).unwrap();
assert_eq!(parsed, *variant, "YAML round-trip failed for {variant:?}");
}
}
#[test]
fn capabilities_s3() {
let s3 = StoragePlatform::S3;
assert!(!s3.supports_atomic_move());
assert!(s3.supports_native_notify());
assert!(s3.supports_versioning());
assert!(s3.supports_lifecycle_rules());
assert_eq!(s3.path_separator(), "/");
assert!(!s3.is_abstract());
}
#[test]
fn capabilities_linux() {
let linux = StoragePlatform::Linux;
assert!(linux.supports_atomic_move());
assert!(linux.supports_native_notify());
assert!(!linux.supports_versioning());
assert!(!linux.supports_lifecycle_rules());
assert_eq!(linux.path_separator(), "/");
assert!(!linux.is_abstract());
}
#[test]
fn capabilities_windows() {
let win = StoragePlatform::Windows;
assert!(win.supports_atomic_move());
assert!(win.supports_native_notify());
assert!(!win.supports_versioning());
assert!(!win.supports_lifecycle_rules());
assert_eq!(win.path_separator(), "\\");
assert!(!win.is_abstract());
}
#[test]
fn capabilities_adls() {
let adls = StoragePlatform::Adls;
assert!(adls.supports_atomic_move());
assert!(adls.supports_native_notify());
assert!(adls.supports_versioning());
assert!(adls.supports_lifecycle_rules());
}
#[test]
fn capabilities_gcs() {
let gcs = StoragePlatform::Gcs;
assert!(!gcs.supports_atomic_move());
assert!(gcs.supports_native_notify());
assert!(gcs.supports_versioning());
assert!(gcs.supports_lifecycle_rules());
}
#[test]
fn capabilities_hdfs() {
let hdfs = StoragePlatform::Hdfs;
assert!(hdfs.supports_atomic_move());
assert!(!hdfs.supports_native_notify());
assert!(!hdfs.supports_versioning());
assert!(!hdfs.supports_lifecycle_rules());
}
#[test]
fn capabilities_juicefs() {
let jfs = StoragePlatform::JuiceFs;
assert!(jfs.supports_atomic_move());
assert!(jfs.supports_native_notify());
assert!(!jfs.supports_versioning());
assert!(jfs.supports_lifecycle_rules());
}
#[test]
fn capabilities_maprfs() {
let mapr = StoragePlatform::MaprFs;
assert!(mapr.supports_atomic_move());
assert!(mapr.supports_native_notify());
assert!(mapr.supports_versioning());
assert!(!mapr.supports_lifecycle_rules());
}
#[test]
fn capabilities_cephfs() {
let ceph = StoragePlatform::CephFs;
assert!(ceph.supports_atomic_move());
assert!(ceph.supports_native_notify());
assert!(ceph.supports_versioning());
assert!(!ceph.supports_lifecycle_rules());
}
#[test]
fn capabilities_seaweedfs() {
let sw = StoragePlatform::SeaweedFs;
assert!(!sw.supports_atomic_move());
assert!(!sw.supports_native_notify());
assert!(!sw.supports_versioning());
assert!(sw.supports_lifecycle_rules());
}
#[test]
fn agnostic_is_abstract() {
assert!(StoragePlatform::Agnostic.is_abstract());
for variant in StoragePlatform::concrete_variants() {
assert!(!variant.is_abstract(), "{variant:?} should not be abstract");
}
}
#[test]
fn display_all_variants() {
assert_eq!(StoragePlatform::Linux.to_string(), "linux");
assert_eq!(StoragePlatform::Windows.to_string(), "windows");
assert_eq!(StoragePlatform::S3.to_string(), "s3");
assert_eq!(StoragePlatform::Adls.to_string(), "adls");
assert_eq!(StoragePlatform::Gcs.to_string(), "gcs");
assert_eq!(StoragePlatform::Hdfs.to_string(), "hdfs");
assert_eq!(StoragePlatform::JuiceFs.to_string(), "juicefs");
assert_eq!(StoragePlatform::MaprFs.to_string(), "maprfs");
assert_eq!(StoragePlatform::CephFs.to_string(), "cephfs");
assert_eq!(StoragePlatform::SeaweedFs.to_string(), "seaweedfs");
assert_eq!(StoragePlatform::Agnostic.to_string(), "agnostic");
}
#[test]
fn from_str_all_variants() {
assert_eq!("linux".parse::<StoragePlatform>().unwrap(), StoragePlatform::Linux);
assert_eq!("S3".parse::<StoragePlatform>().unwrap(), StoragePlatform::S3);
assert_eq!("ADLS".parse::<StoragePlatform>().unwrap(), StoragePlatform::Adls);
assert_eq!("GCS".parse::<StoragePlatform>().unwrap(), StoragePlatform::Gcs);
assert_eq!("HdFs".parse::<StoragePlatform>().unwrap(), StoragePlatform::Hdfs);
assert_eq!("agnostic".parse::<StoragePlatform>().unwrap(), StoragePlatform::Agnostic);
}
#[test]
fn from_str_unknown_returns_error() {
let result = "unknown".parse::<StoragePlatform>();
assert!(result.is_err());
assert!(result.unwrap_err().contains("Unknown storage platform"));
}
#[test]
fn all_variants_has_correct_count() {
assert_eq!(StoragePlatform::all_variants().len(), 11);
}
#[test]
fn concrete_variants_excludes_agnostic() {
let concrete = StoragePlatform::concrete_variants();
assert_eq!(concrete.len(), 10);
assert!(!concrete.contains(&StoragePlatform::Agnostic));
}
#[test]
fn all_variants_have_non_empty_path_separator() {
for variant in StoragePlatform::all_variants() {
let sep = variant.path_separator();
assert!(!sep.is_empty(), "{variant:?} has empty separator");
}
}
#[test]
fn display_round_trips_through_from_str() {
for variant in StoragePlatform::all_variants() {
let display = variant.to_string();
let parsed: StoragePlatform = display.parse().unwrap();
assert_eq!(parsed, *variant, "Display → FromStr round-trip failed for {variant:?}");
}
}
}