#![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(['_', ' '], "-")
}
fn non_empty_text(value: impl AsRef<str>) -> Result<String, CellNameError> {
let trimmed = value.as_ref().trim();
if trimmed.is_empty() {
Err(CellNameError::Empty)
} else {
Ok(trimmed.to_string())
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CellNameError {
Empty,
}
impl fmt::Display for CellNameError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("cell label cannot be empty"),
}
}
}
impl Error for CellNameError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum CellKind {
Prokaryotic,
Eukaryotic,
Animal,
Plant,
Fungal,
Bacterial,
Archaeal,
Stem,
Somatic,
Germ,
Unknown,
Custom(String),
}
impl fmt::Display for CellKind {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Prokaryotic => formatter.write_str("prokaryotic"),
Self::Eukaryotic => formatter.write_str("eukaryotic"),
Self::Animal => formatter.write_str("animal"),
Self::Plant => formatter.write_str("plant"),
Self::Fungal => formatter.write_str("fungal"),
Self::Bacterial => formatter.write_str("bacterial"),
Self::Archaeal => formatter.write_str("archaeal"),
Self::Stem => formatter.write_str("stem"),
Self::Somatic => formatter.write_str("somatic"),
Self::Germ => formatter.write_str("germ"),
Self::Unknown => formatter.write_str("unknown"),
Self::Custom(value) => formatter.write_str(value),
}
}
}
impl FromStr for CellKind {
type Err = CellKindParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(CellKindParseError::Empty);
}
match normalized_key(trimmed).as_str() {
"prokaryotic" => Ok(Self::Prokaryotic),
"eukaryotic" => Ok(Self::Eukaryotic),
"animal" => Ok(Self::Animal),
"plant" => Ok(Self::Plant),
"fungal" => Ok(Self::Fungal),
"bacterial" => Ok(Self::Bacterial),
"archaeal" => Ok(Self::Archaeal),
"stem" => Ok(Self::Stem),
"somatic" => Ok(Self::Somatic),
"germ" => Ok(Self::Germ),
"unknown" => Ok(Self::Unknown),
_ => Ok(Self::Custom(trimmed.to_string())),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CellKindParseError {
Empty,
}
impl fmt::Display for CellKindParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("cell kind cannot be empty"),
}
}
}
impl Error for CellKindParseError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum CellOrganelle {
Nucleus,
Mitochondrion,
Chloroplast,
Ribosome,
CellMembrane,
CellWall,
Cytoplasm,
GolgiApparatus,
EndoplasmicReticulum,
Vacuole,
Unknown,
Custom(String),
}
impl fmt::Display for CellOrganelle {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Nucleus => formatter.write_str("nucleus"),
Self::Mitochondrion => formatter.write_str("mitochondrion"),
Self::Chloroplast => formatter.write_str("chloroplast"),
Self::Ribosome => formatter.write_str("ribosome"),
Self::CellMembrane => formatter.write_str("cell-membrane"),
Self::CellWall => formatter.write_str("cell-wall"),
Self::Cytoplasm => formatter.write_str("cytoplasm"),
Self::GolgiApparatus => formatter.write_str("golgi-apparatus"),
Self::EndoplasmicReticulum => formatter.write_str("endoplasmic-reticulum"),
Self::Vacuole => formatter.write_str("vacuole"),
Self::Unknown => formatter.write_str("unknown"),
Self::Custom(value) => formatter.write_str(value),
}
}
}
impl FromStr for CellOrganelle {
type Err = CellOrganelleParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(CellOrganelleParseError::Empty);
}
match normalized_key(trimmed).as_str() {
"nucleus" => Ok(Self::Nucleus),
"mitochondrion" | "mitochondria" => Ok(Self::Mitochondrion),
"chloroplast" => Ok(Self::Chloroplast),
"ribosome" => Ok(Self::Ribosome),
"cell-membrane" => Ok(Self::CellMembrane),
"cell-wall" => Ok(Self::CellWall),
"cytoplasm" => Ok(Self::Cytoplasm),
"golgi-apparatus" | "golgi" => Ok(Self::GolgiApparatus),
"endoplasmic-reticulum" => Ok(Self::EndoplasmicReticulum),
"vacuole" => Ok(Self::Vacuole),
"unknown" => Ok(Self::Unknown),
_ => Ok(Self::Custom(trimmed.to_string())),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CellOrganelleParseError {
Empty,
}
impl fmt::Display for CellOrganelleParseError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Empty => formatter.write_str("cell organelle cannot be empty"),
}
}
}
impl Error for CellOrganelleParseError {}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct CellType {
name: String,
kind: CellKind,
}
impl CellType {
pub fn new(name: impl AsRef<str>, kind: CellKind) -> Result<Self, CellNameError> {
Ok(Self {
name: non_empty_text(name)?,
kind,
})
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub const fn kind(&self) -> &CellKind {
&self.kind
}
}
impl fmt::Display for CellType {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.name())
}
}
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum CellStructure {
Organelle(CellOrganelle),
Custom(String),
}
impl CellStructure {
pub fn custom(value: impl AsRef<str>) -> Result<Self, CellNameError> {
non_empty_text(value).map(Self::Custom)
}
}
impl From<CellOrganelle> for CellStructure {
fn from(organelle: CellOrganelle) -> Self {
Self::Organelle(organelle)
}
}
impl fmt::Display for CellStructure {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Organelle(organelle) => organelle.fmt(formatter),
Self::Custom(value) => formatter.write_str(value),
}
}
}
#[cfg(test)]
mod tests {
use super::{
CellKind, CellKindParseError, CellNameError, CellOrganelle, CellOrganelleParseError,
CellStructure, CellType,
};
#[test]
fn displays_and_parses_cell_kind() -> Result<(), CellKindParseError> {
assert_eq!(CellKind::Eukaryotic.to_string(), "eukaryotic");
assert_eq!("plant".parse::<CellKind>()?, CellKind::Plant);
assert_eq!("stem".parse::<CellKind>()?, CellKind::Stem);
Ok(())
}
#[test]
fn displays_and_parses_organelles() -> Result<(), CellOrganelleParseError> {
assert_eq!(CellOrganelle::CellWall.to_string(), "cell-wall");
assert_eq!(
"cell membrane".parse::<CellOrganelle>()?,
CellOrganelle::CellMembrane
);
assert_eq!(
"golgi".parse::<CellOrganelle>()?,
CellOrganelle::GolgiApparatus
);
Ok(())
}
#[test]
fn parses_custom_cell_kind() -> Result<(), CellKindParseError> {
assert_eq!(
"guard cell".parse::<CellKind>()?,
CellKind::Custom("guard cell".to_string())
);
assert_eq!(" ".parse::<CellKind>(), Err(CellKindParseError::Empty));
Ok(())
}
#[test]
fn parses_custom_organelle() -> Result<(), CellOrganelleParseError> {
assert_eq!(
"eyespot".parse::<CellOrganelle>()?,
CellOrganelle::Custom("eyespot".to_string())
);
assert_eq!(
"".parse::<CellOrganelle>(),
Err(CellOrganelleParseError::Empty)
);
Ok(())
}
#[test]
fn cell_type_and_structure_display_stably() -> Result<(), CellNameError> {
let cell_type = CellType::new("neuron", CellKind::Animal)?;
let structure = CellStructure::from(CellOrganelle::Nucleus);
let custom = CellStructure::custom("contractile vacuole")?;
assert_eq!(cell_type.name(), "neuron");
assert_eq!(cell_type.kind(), &CellKind::Animal);
assert_eq!(cell_type.to_string(), "neuron");
assert_eq!(structure.to_string(), "nucleus");
assert_eq!(custom.to_string(), "contractile vacuole");
Ok(())
}
}