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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub enum CellNameError {
24 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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
40pub enum CellKind {
41 Prokaryotic,
43 Eukaryotic,
45 Animal,
47 Plant,
49 Fungal,
51 Bacterial,
53 Archaeal,
55 Stem,
57 Somatic,
59 Germ,
61 Unknown,
63 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
115pub enum CellKindParseError {
116 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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
132pub enum CellOrganelle {
133 Nucleus,
135 Mitochondrion,
137 Chloroplast,
139 Ribosome,
141 CellMembrane,
143 CellWall,
145 Cytoplasm,
147 GolgiApparatus,
149 EndoplasmicReticulum,
151 Vacuole,
153 Unknown,
155 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
207pub enum CellOrganelleParseError {
208 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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
224pub struct CellType {
225 name: String,
226 kind: CellKind,
227}
228
229impl CellType {
230 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 #[must_use]
244 pub fn name(&self) -> &str {
245 &self.name
246 }
247
248 #[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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
263pub enum CellStructure {
264 Organelle(CellOrganelle),
266 Custom(String),
268}
269
270impl CellStructure {
271 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}