#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::{fmt, str::FromStr};
use std::error::Error;
fn normalized_key(value: &str) -> String {
value.trim().to_ascii_lowercase().replace(['_', ' '], "-")
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BiologicalSystemNameError {
Empty,
}
impl fmt::Display for BiologicalSystemNameError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("biological system name cannot be empty"),
}
}
}
impl Error for BiologicalSystemNameError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum BiologicalSystemKind {
Nervous,
Circulatory,
Respiratory,
Digestive,
Endocrine,
Immune,
Reproductive,
Musculoskeletal,
RootSystem,
ShootSystem,
VascularSystem,
Unknown,
Custom(String),
}
impl fmt::Display for BiologicalSystemKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Nervous => formatter.write_str("nervous"),
Self::Circulatory => formatter.write_str("circulatory"),
Self::Respiratory => formatter.write_str("respiratory"),
Self::Digestive => formatter.write_str("digestive"),
Self::Endocrine => formatter.write_str("endocrine"),
Self::Immune => formatter.write_str("immune"),
Self::Reproductive => formatter.write_str("reproductive"),
Self::Musculoskeletal => formatter.write_str("musculoskeletal"),
Self::RootSystem => formatter.write_str("root-system"),
Self::ShootSystem => formatter.write_str("shoot-system"),
Self::VascularSystem => formatter.write_str("vascular-system"),
Self::Unknown => formatter.write_str("unknown"),
Self::Custom(value) => formatter.write_str(value),
}
}
}
impl FromStr for BiologicalSystemKind {
type Err = BiologicalSystemKindParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(BiologicalSystemKindParseError::Empty);
}
match normalized_key(trimmed).as_str() {
"nervous" | "nervous-system" => Ok(Self::Nervous),
"circulatory" | "circulatory-system" => Ok(Self::Circulatory),
"respiratory" | "respiratory-system" => Ok(Self::Respiratory),
"digestive" | "digestive-system" => Ok(Self::Digestive),
"endocrine" | "endocrine-system" => Ok(Self::Endocrine),
"immune" | "immune-system" => Ok(Self::Immune),
"reproductive" | "reproductive-system" => Ok(Self::Reproductive),
"musculoskeletal" | "musculoskeletal-system" => Ok(Self::Musculoskeletal),
"root-system" => Ok(Self::RootSystem),
"shoot-system" => Ok(Self::ShootSystem),
"vascular-system" => Ok(Self::VascularSystem),
"unknown" => Ok(Self::Unknown),
_ => Ok(Self::Custom(trimmed.to_string())),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BiologicalSystemKindParseError {
Empty,
}
impl fmt::Display for BiologicalSystemKindParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("biological system kind cannot be empty"),
}
}
}
impl Error for BiologicalSystemKindParseError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct BiologicalSystem {
name: String,
kind: BiologicalSystemKind,
}
impl BiologicalSystem {
pub fn new(
name: impl AsRef<str>,
kind: BiologicalSystemKind,
) -> Result<Self, BiologicalSystemNameError> {
let trimmed = name.as_ref().trim();
if trimmed.is_empty() {
return Err(BiologicalSystemNameError::Empty);
}
Ok(Self {
name: trimmed.to_string(),
kind,
})
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub const fn kind(&self) -> &BiologicalSystemKind {
&self.kind
}
}
impl fmt::Display for BiologicalSystem {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.name())
}
}
#[cfg(test)]
mod tests {
use super::{
BiologicalSystem, BiologicalSystemKind, BiologicalSystemKindParseError,
BiologicalSystemNameError,
};
#[test]
fn constructs_valid_biological_system_name() -> Result<(), BiologicalSystemNameError> {
let system = BiologicalSystem::new("root system", BiologicalSystemKind::RootSystem)?;
assert_eq!(system.name(), "root system");
assert_eq!(system.kind(), &BiologicalSystemKind::RootSystem);
Ok(())
}
#[test]
fn rejects_empty_system_name() {
assert_eq!(
BiologicalSystem::new(" ", BiologicalSystemKind::Unknown),
Err(BiologicalSystemNameError::Empty)
);
}
#[test]
fn displays_and_parses_system_kind() -> Result<(), BiologicalSystemKindParseError> {
assert_eq!(BiologicalSystemKind::Nervous.to_string(), "nervous");
assert_eq!(
"respiratory system".parse::<BiologicalSystemKind>()?,
BiologicalSystemKind::Respiratory
);
Ok(())
}
#[test]
fn parses_plant_system_variants() -> Result<(), BiologicalSystemKindParseError> {
assert_eq!(
"root system".parse::<BiologicalSystemKind>()?,
BiologicalSystemKind::RootSystem
);
assert_eq!(
"shoot-system".parse::<BiologicalSystemKind>()?,
BiologicalSystemKind::ShootSystem
);
assert_eq!(
"vascular system".parse::<BiologicalSystemKind>()?,
BiologicalSystemKind::VascularSystem
);
Ok(())
}
#[test]
fn parses_custom_system_kind() -> Result<(), BiologicalSystemKindParseError> {
assert_eq!(
"water-vascular".parse::<BiologicalSystemKind>()?,
BiologicalSystemKind::Custom("water-vascular".to_string())
);
assert_eq!(
"".parse::<BiologicalSystemKind>(),
Err(BiologicalSystemKindParseError::Empty)
);
Ok(())
}
}