use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum TypeClass {
Llm,
Rule,
Human,
Composite,
Sensor,
Actuator,
Hybrid,
Extension(ExtensionClass),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ExtensionClass(String);
impl ExtensionClass {
pub fn new(name: &str) -> Result<Self, &'static str> {
if name.is_empty() {
return Err("extension class name cannot be empty");
}
if name.len() < 2 {
return Err("extension class name must be at least 2 characters");
}
if !name.chars().all(|c| c.is_ascii_lowercase()) {
return Err("extension class name must be all lowercase letters");
}
Ok(Self(name.to_string()))
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl fmt::Display for ExtensionClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl TypeClass {
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Self::Llm => "llm",
Self::Rule => "rule",
Self::Human => "human",
Self::Composite => "composite",
Self::Sensor => "sensor",
Self::Actuator => "actuator",
Self::Hybrid => "hybrid",
Self::Extension(ext) => ext.as_str(),
}
}
#[must_use]
pub const fn is_core(&self) -> bool {
!matches!(self, Self::Extension(_))
}
}
impl fmt::Display for TypeClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl FromStr for TypeClass {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"llm" => Ok(Self::Llm),
"rule" => Ok(Self::Rule),
"human" => Ok(Self::Human),
"composite" => Ok(Self::Composite),
"sensor" => Ok(Self::Sensor),
"actuator" => Ok(Self::Actuator),
"hybrid" => Ok(Self::Hybrid),
other => {
let ext = ExtensionClass::new(other)?;
Ok(Self::Extension(ext))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_core_classes() {
assert_eq!("llm".parse::<TypeClass>().unwrap(), TypeClass::Llm);
assert_eq!("rule".parse::<TypeClass>().unwrap(), TypeClass::Rule);
assert_eq!("human".parse::<TypeClass>().unwrap(), TypeClass::Human);
assert_eq!("composite".parse::<TypeClass>().unwrap(), TypeClass::Composite);
assert_eq!("sensor".parse::<TypeClass>().unwrap(), TypeClass::Sensor);
assert_eq!("actuator".parse::<TypeClass>().unwrap(), TypeClass::Actuator);
assert_eq!("hybrid".parse::<TypeClass>().unwrap(), TypeClass::Hybrid);
}
#[test]
fn core_classes_are_core() {
assert!(TypeClass::Llm.is_core());
assert!(TypeClass::Rule.is_core());
assert!(TypeClass::Human.is_core());
}
#[test]
fn parse_extension_class() {
let tc = "custom".parse::<TypeClass>().unwrap();
assert!(matches!(tc, TypeClass::Extension(_)));
assert!(!tc.is_core());
assert_eq!(tc.as_str(), "custom");
}
#[test]
fn extension_class_validation() {
assert!(ExtensionClass::new("a").is_err());
assert!(ExtensionClass::new("").is_err());
assert!(ExtensionClass::new("Custom").is_err());
assert!(ExtensionClass::new("v2").is_err());
assert!(ExtensionClass::new("custom").is_ok());
}
}