use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum AntigenCategory {
SubstrateAlignment,
FunctionalCorrectness,
}
impl AntigenCategory {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::SubstrateAlignment => "substrate-alignment",
Self::FunctionalCorrectness => "functional-correctness",
}
}
#[must_use]
pub fn parse_category(s: &str) -> Option<Self> {
match s {
"substrate-alignment"
| "SubstrateAlignment"
| "AntigenCategory::SubstrateAlignment" => Some(Self::SubstrateAlignment),
"functional-correctness"
| "FunctionalCorrectness"
| "AntigenCategory::FunctionalCorrectness" => Some(Self::FunctionalCorrectness),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn category_str_roundtrip() {
for variant in [
AntigenCategory::SubstrateAlignment,
AntigenCategory::FunctionalCorrectness,
] {
let s = variant.as_str();
let back = AntigenCategory::parse_category(s).expect("kebab roundtrip");
assert_eq!(back, variant);
}
}
#[test]
fn category_parses_all_forms() {
assert_eq!(
AntigenCategory::parse_category("substrate-alignment"),
Some(AntigenCategory::SubstrateAlignment)
);
assert_eq!(
AntigenCategory::parse_category("SubstrateAlignment"),
Some(AntigenCategory::SubstrateAlignment)
);
assert_eq!(
AntigenCategory::parse_category("AntigenCategory::SubstrateAlignment"),
Some(AntigenCategory::SubstrateAlignment)
);
assert_eq!(
AntigenCategory::parse_category("functional-correctness"),
Some(AntigenCategory::FunctionalCorrectness)
);
assert_eq!(
AntigenCategory::parse_category("FunctionalCorrectness"),
Some(AntigenCategory::FunctionalCorrectness)
);
assert_eq!(AntigenCategory::parse_category("unknown"), None);
}
#[test]
fn category_rejects_snake_case() {
assert_eq!(AntigenCategory::parse_category("substrate_alignment"), None);
assert_eq!(
AntigenCategory::parse_category("functional_correctness"),
None
);
}
#[test]
fn category_lib_parser_is_intentional_superset_of_macro_parser() {
for variant in [
AntigenCategory::SubstrateAlignment,
AntigenCategory::FunctionalCorrectness,
] {
let pascal = match variant {
AntigenCategory::SubstrateAlignment => "SubstrateAlignment",
AntigenCategory::FunctionalCorrectness => "FunctionalCorrectness",
};
let path = match variant {
AntigenCategory::SubstrateAlignment => "AntigenCategory::SubstrateAlignment",
AntigenCategory::FunctionalCorrectness => "AntigenCategory::FunctionalCorrectness",
};
assert_eq!(AntigenCategory::parse_category(pascal), Some(variant));
assert_eq!(AntigenCategory::parse_category(path), Some(variant));
assert_eq!(
AntigenCategory::parse_category(variant.as_str()),
Some(variant),
"lib parser must accept its own kebab `as_str` output (CLI/serde form)"
);
}
}
#[test]
fn category_is_copy() {
let c = AntigenCategory::SubstrateAlignment;
let d = c; assert_eq!(c, d);
}
#[test]
fn category_rejects_empty_string() {
assert_eq!(AntigenCategory::parse_category(""), None);
}
#[test]
fn category_rejects_whitespace_padded() {
assert_eq!(
AntigenCategory::parse_category("substrate-alignment "),
None
);
assert_eq!(AntigenCategory::parse_category(" SubstrateAlignment"), None);
}
#[test]
fn category_rejects_mixed_separator() {
assert_eq!(AntigenCategory::parse_category("substrate-Alignment"), None);
assert_eq!(AntigenCategory::parse_category("Substrate-alignment"), None);
}
#[test]
fn category_rejects_partial_path() {
assert_eq!(
AntigenCategory::parse_category("Category::SubstrateAlignment"),
None
);
}
#[test]
fn category_serde_roundtrip() {
let s = serde_json::to_string(&AntigenCategory::SubstrateAlignment).unwrap();
assert_eq!(s, "\"substrate-alignment\"");
let back: AntigenCategory = serde_json::from_str("\"substrate-alignment\"").unwrap();
assert_eq!(back, AntigenCategory::SubstrateAlignment);
}
#[test]
fn category_serde_rejects_unknown_variant() {
let result: Result<AntigenCategory, _> = serde_json::from_str("\"hybrid\"");
assert!(
result.is_err(),
"serde should reject unknown variant 'hybrid', got Ok"
);
}
#[test]
fn category_serde_rejects_uppercase_form() {
let result: Result<AntigenCategory, _> = serde_json::from_str("\"SubstrateAlignment\"");
assert!(
result.is_err(),
"serde should reject PascalCase 'SubstrateAlignment'; canonical is 'substrate-alignment'"
);
}
#[test]
fn category_rejects_null_byte() {
assert_eq!(AntigenCategory::parse_category("\0"), None);
assert_eq!(
AntigenCategory::parse_category("substrate-alignment\0"),
None
);
}
#[test]
fn category_hash_consistent_with_eq() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(AntigenCategory::SubstrateAlignment);
set.insert(AntigenCategory::SubstrateAlignment);
assert_eq!(set.len(), 1);
set.insert(AntigenCategory::FunctionalCorrectness);
assert_eq!(set.len(), 2);
}
}