Skip to main content

use_cell/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7fn normalized_key(value: &str) -> String {
8    value.trim().to_ascii_lowercase().replace(['_', ' '], "-")
9}
10
11fn non_empty_text(value: impl AsRef<str>) -> Result<String, CellNameError> {
12    let trimmed = value.as_ref().trim();
13
14    if trimmed.is_empty() {
15        Err(CellNameError::Empty)
16    } else {
17        Ok(trimmed.to_string())
18    }
19}
20
21/// Error returned when cell labels are empty.
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub enum CellNameError {
24    /// The supplied value was empty after trimming surrounding whitespace.
25    Empty,
26}
27
28impl fmt::Display for CellNameError {
29    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
30        match self {
31            Self::Empty => formatter.write_str("cell label cannot be empty"),
32        }
33    }
34}
35
36impl Error for CellNameError {}
37
38/// Broad cell kind vocabulary.
39#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
40pub enum CellKind {
41    /// Prokaryotic cells.
42    Prokaryotic,
43    /// Eukaryotic cells.
44    Eukaryotic,
45    /// Animal cells.
46    Animal,
47    /// Plant cells.
48    Plant,
49    /// Fungal cells.
50    Fungal,
51    /// Bacterial cells.
52    Bacterial,
53    /// Archaeal cells.
54    Archaeal,
55    /// Stem cells.
56    Stem,
57    /// Somatic cells.
58    Somatic,
59    /// Germ cells.
60    Germ,
61    /// Unknown cell kind.
62    Unknown,
63    /// Caller-defined cell kind text.
64    Custom(String),
65}
66
67impl fmt::Display for CellKind {
68    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
69        match self {
70            Self::Prokaryotic => formatter.write_str("prokaryotic"),
71            Self::Eukaryotic => formatter.write_str("eukaryotic"),
72            Self::Animal => formatter.write_str("animal"),
73            Self::Plant => formatter.write_str("plant"),
74            Self::Fungal => formatter.write_str("fungal"),
75            Self::Bacterial => formatter.write_str("bacterial"),
76            Self::Archaeal => formatter.write_str("archaeal"),
77            Self::Stem => formatter.write_str("stem"),
78            Self::Somatic => formatter.write_str("somatic"),
79            Self::Germ => formatter.write_str("germ"),
80            Self::Unknown => formatter.write_str("unknown"),
81            Self::Custom(value) => formatter.write_str(value),
82        }
83    }
84}
85
86impl FromStr for CellKind {
87    type Err = CellKindParseError;
88
89    fn from_str(value: &str) -> Result<Self, Self::Err> {
90        let trimmed = value.trim();
91
92        if trimmed.is_empty() {
93            return Err(CellKindParseError::Empty);
94        }
95
96        match normalized_key(trimmed).as_str() {
97            "prokaryotic" => Ok(Self::Prokaryotic),
98            "eukaryotic" => Ok(Self::Eukaryotic),
99            "animal" => Ok(Self::Animal),
100            "plant" => Ok(Self::Plant),
101            "fungal" => Ok(Self::Fungal),
102            "bacterial" => Ok(Self::Bacterial),
103            "archaeal" => Ok(Self::Archaeal),
104            "stem" => Ok(Self::Stem),
105            "somatic" => Ok(Self::Somatic),
106            "germ" => Ok(Self::Germ),
107            "unknown" => Ok(Self::Unknown),
108            _ => Ok(Self::Custom(trimmed.to_string())),
109        }
110    }
111}
112
113/// Error returned when parsing a cell kind fails.
114#[derive(Clone, Copy, Debug, Eq, PartialEq)]
115pub enum CellKindParseError {
116    /// The cell kind was empty after trimming whitespace.
117    Empty,
118}
119
120impl fmt::Display for CellKindParseError {
121    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
122        match self {
123            Self::Empty => formatter.write_str("cell kind cannot be empty"),
124        }
125    }
126}
127
128impl Error for CellKindParseError {}
129
130/// Primitive cell organelle vocabulary.
131#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
132pub enum CellOrganelle {
133    /// Nucleus.
134    Nucleus,
135    /// Mitochondrion.
136    Mitochondrion,
137    /// Chloroplast.
138    Chloroplast,
139    /// Ribosome.
140    Ribosome,
141    /// Cell membrane.
142    CellMembrane,
143    /// Cell wall.
144    CellWall,
145    /// Cytoplasm.
146    Cytoplasm,
147    /// Golgi apparatus.
148    GolgiApparatus,
149    /// Endoplasmic reticulum.
150    EndoplasmicReticulum,
151    /// Vacuole.
152    Vacuole,
153    /// Unknown organelle.
154    Unknown,
155    /// Caller-defined organelle text.
156    Custom(String),
157}
158
159impl fmt::Display for CellOrganelle {
160    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
161        match self {
162            Self::Nucleus => formatter.write_str("nucleus"),
163            Self::Mitochondrion => formatter.write_str("mitochondrion"),
164            Self::Chloroplast => formatter.write_str("chloroplast"),
165            Self::Ribosome => formatter.write_str("ribosome"),
166            Self::CellMembrane => formatter.write_str("cell-membrane"),
167            Self::CellWall => formatter.write_str("cell-wall"),
168            Self::Cytoplasm => formatter.write_str("cytoplasm"),
169            Self::GolgiApparatus => formatter.write_str("golgi-apparatus"),
170            Self::EndoplasmicReticulum => formatter.write_str("endoplasmic-reticulum"),
171            Self::Vacuole => formatter.write_str("vacuole"),
172            Self::Unknown => formatter.write_str("unknown"),
173            Self::Custom(value) => formatter.write_str(value),
174        }
175    }
176}
177
178impl FromStr for CellOrganelle {
179    type Err = CellOrganelleParseError;
180
181    fn from_str(value: &str) -> Result<Self, Self::Err> {
182        let trimmed = value.trim();
183
184        if trimmed.is_empty() {
185            return Err(CellOrganelleParseError::Empty);
186        }
187
188        match normalized_key(trimmed).as_str() {
189            "nucleus" => Ok(Self::Nucleus),
190            "mitochondrion" | "mitochondria" => Ok(Self::Mitochondrion),
191            "chloroplast" => Ok(Self::Chloroplast),
192            "ribosome" => Ok(Self::Ribosome),
193            "cell-membrane" => Ok(Self::CellMembrane),
194            "cell-wall" => Ok(Self::CellWall),
195            "cytoplasm" => Ok(Self::Cytoplasm),
196            "golgi-apparatus" | "golgi" => Ok(Self::GolgiApparatus),
197            "endoplasmic-reticulum" => Ok(Self::EndoplasmicReticulum),
198            "vacuole" => Ok(Self::Vacuole),
199            "unknown" => Ok(Self::Unknown),
200            _ => Ok(Self::Custom(trimmed.to_string())),
201        }
202    }
203}
204
205/// Error returned when parsing a cell organelle fails.
206#[derive(Clone, Copy, Debug, Eq, PartialEq)]
207pub enum CellOrganelleParseError {
208    /// The organelle text was empty after trimming whitespace.
209    Empty,
210}
211
212impl fmt::Display for CellOrganelleParseError {
213    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
214        match self {
215            Self::Empty => formatter.write_str("cell organelle cannot be empty"),
216        }
217    }
218}
219
220impl Error for CellOrganelleParseError {}
221
222/// A descriptive cell type label with a broad cell kind.
223#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
224pub struct CellType {
225    name: String,
226    kind: CellKind,
227}
228
229impl CellType {
230    /// Creates a cell type from non-empty text and a cell kind.
231    ///
232    /// # Errors
233    ///
234    /// Returns [`CellNameError::Empty`] when the trimmed name is empty.
235    pub fn new(name: impl AsRef<str>, kind: CellKind) -> Result<Self, CellNameError> {
236        Ok(Self {
237            name: non_empty_text(name)?,
238            kind,
239        })
240    }
241
242    /// Returns the cell type label.
243    #[must_use]
244    pub fn name(&self) -> &str {
245        &self.name
246    }
247
248    /// Returns the broad cell kind.
249    #[must_use]
250    pub const fn kind(&self) -> &CellKind {
251        &self.kind
252    }
253}
254
255impl fmt::Display for CellType {
256    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
257        formatter.write_str(self.name())
258    }
259}
260
261/// A primitive cell structure reference.
262#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
263pub enum CellStructure {
264    /// A named organelle.
265    Organelle(CellOrganelle),
266    /// Caller-defined structure label.
267    Custom(String),
268}
269
270impl CellStructure {
271    /// Creates a custom structure label from non-empty text.
272    ///
273    /// # Errors
274    ///
275    /// Returns [`CellNameError::Empty`] when the trimmed label is empty.
276    pub fn custom(value: impl AsRef<str>) -> Result<Self, CellNameError> {
277        non_empty_text(value).map(Self::Custom)
278    }
279}
280
281impl From<CellOrganelle> for CellStructure {
282    fn from(organelle: CellOrganelle) -> Self {
283        Self::Organelle(organelle)
284    }
285}
286
287impl fmt::Display for CellStructure {
288    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
289        match self {
290            Self::Organelle(organelle) => organelle.fmt(formatter),
291            Self::Custom(value) => formatter.write_str(value),
292        }
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use super::{
299        CellKind, CellKindParseError, CellNameError, CellOrganelle, CellOrganelleParseError,
300        CellStructure, CellType,
301    };
302
303    #[test]
304    fn displays_and_parses_cell_kind() -> Result<(), CellKindParseError> {
305        assert_eq!(CellKind::Eukaryotic.to_string(), "eukaryotic");
306        assert_eq!("plant".parse::<CellKind>()?, CellKind::Plant);
307        assert_eq!("stem".parse::<CellKind>()?, CellKind::Stem);
308        Ok(())
309    }
310
311    #[test]
312    fn displays_and_parses_organelles() -> Result<(), CellOrganelleParseError> {
313        assert_eq!(CellOrganelle::CellWall.to_string(), "cell-wall");
314        assert_eq!(
315            "cell membrane".parse::<CellOrganelle>()?,
316            CellOrganelle::CellMembrane
317        );
318        assert_eq!(
319            "golgi".parse::<CellOrganelle>()?,
320            CellOrganelle::GolgiApparatus
321        );
322        Ok(())
323    }
324
325    #[test]
326    fn parses_custom_cell_kind() -> Result<(), CellKindParseError> {
327        assert_eq!(
328            "guard cell".parse::<CellKind>()?,
329            CellKind::Custom("guard cell".to_string())
330        );
331        assert_eq!(" ".parse::<CellKind>(), Err(CellKindParseError::Empty));
332        Ok(())
333    }
334
335    #[test]
336    fn parses_custom_organelle() -> Result<(), CellOrganelleParseError> {
337        assert_eq!(
338            "eyespot".parse::<CellOrganelle>()?,
339            CellOrganelle::Custom("eyespot".to_string())
340        );
341        assert_eq!(
342            "".parse::<CellOrganelle>(),
343            Err(CellOrganelleParseError::Empty)
344        );
345        Ok(())
346    }
347
348    #[test]
349    fn cell_type_and_structure_display_stably() -> Result<(), CellNameError> {
350        let cell_type = CellType::new("neuron", CellKind::Animal)?;
351        let structure = CellStructure::from(CellOrganelle::Nucleus);
352        let custom = CellStructure::custom("contractile vacuole")?;
353
354        assert_eq!(cell_type.name(), "neuron");
355        assert_eq!(cell_type.kind(), &CellKind::Animal);
356        assert_eq!(cell_type.to_string(), "neuron");
357        assert_eq!(structure.to_string(), "nucleus");
358        assert_eq!(custom.to_string(), "contractile vacuole");
359        Ok(())
360    }
361}