use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tier {
Core,
Extended,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Category {
Cache,
Build,
Log,
Media,
Vcs,
Ide,
Other,
Archive,
Installer,
VmImage,
ModelCache,
Backup,
}
impl Category {
pub const ALL: &'static [Category] = &[
Category::Cache,
Category::Build,
Category::Log,
Category::Media,
Category::Vcs,
Category::Ide,
Category::Other,
Category::Archive,
Category::Installer,
Category::VmImage,
Category::ModelCache,
Category::Backup,
];
pub fn label(&self) -> &'static str {
match self {
Category::Cache => "cache",
Category::Build => "build",
Category::Log => "log",
Category::Media => "media",
Category::Vcs => "vcs",
Category::Ide => "ide",
Category::Other => "other",
Category::Archive => "archive",
Category::Installer => "installer",
Category::VmImage => "vm_image",
Category::ModelCache => "model_cache",
Category::Backup => "backup",
}
}
pub fn tier(&self) -> Tier {
match self {
Category::Cache
| Category::Build
| Category::Log
| Category::Media
| Category::Vcs
| Category::Ide
| Category::Other => Tier::Core,
Category::Archive
| Category::Installer
| Category::VmImage
| Category::ModelCache
| Category::Backup => Tier::Extended,
}
}
}
impl fmt::Display for Category {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.label())
}
}
impl std::str::FromStr for Category {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let lower = s.to_ascii_lowercase();
Category::ALL
.iter()
.copied()
.find(|c| c.label() == lower)
.ok_or_else(|| {
let labels: Vec<&str> = Category::ALL.iter().map(|c| c.label()).collect();
format!(
"invalid category '{s}' (expected one of: {})",
labels.join(", ")
)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tier_split_matches_intent() {
for c in [
Category::Cache,
Category::Build,
Category::Log,
Category::Media,
Category::Vcs,
Category::Ide,
Category::Other,
] {
assert_eq!(c.tier(), Tier::Core, "{c:?} should be Core");
}
for c in [
Category::Archive,
Category::Installer,
Category::VmImage,
Category::ModelCache,
Category::Backup,
] {
assert_eq!(c.tier(), Tier::Extended, "{c:?} should be Extended");
}
}
#[test]
fn category_display_round_trips_through_from_str() {
for &c in Category::ALL {
let s = c.to_string();
let parsed: Category = s.parse().expect("label() must parse back via FromStr");
assert_eq!(parsed, c, "round-trip mismatch for {c:?} via '{s}'");
}
}
#[test]
fn category_from_str_is_case_insensitive() {
assert_eq!("CACHE".parse::<Category>().unwrap(), Category::Cache);
assert_eq!("Vm_Image".parse::<Category>().unwrap(), Category::VmImage);
}
#[test]
fn category_from_str_rejects_unknown_with_full_list() {
let err = "bogus".parse::<Category>().unwrap_err();
for c in Category::ALL {
assert!(
err.contains(c.label()),
"error message missing '{}': {err}",
c.label()
);
}
}
}