Skip to main content

use_reproduction/
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
11/// Reproduction mode vocabulary.
12#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
13pub enum ReproductionMode {
14    /// Asexual reproduction.
15    Asexual,
16    /// Sexual reproduction.
17    Sexual,
18    /// Budding.
19    Budding,
20    /// Fragmentation.
21    Fragmentation,
22    /// Spore formation.
23    SporeFormation,
24    /// Binary fission.
25    BinaryFission,
26    /// Pollination.
27    Pollination,
28    /// Unknown reproduction mode.
29    Unknown,
30    /// Caller-defined reproduction mode text.
31    Custom(String),
32}
33
34impl fmt::Display for ReproductionMode {
35    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            Self::Asexual => formatter.write_str("asexual"),
38            Self::Sexual => formatter.write_str("sexual"),
39            Self::Budding => formatter.write_str("budding"),
40            Self::Fragmentation => formatter.write_str("fragmentation"),
41            Self::SporeFormation => formatter.write_str("spore-formation"),
42            Self::BinaryFission => formatter.write_str("binary-fission"),
43            Self::Pollination => formatter.write_str("pollination"),
44            Self::Unknown => formatter.write_str("unknown"),
45            Self::Custom(value) => formatter.write_str(value),
46        }
47    }
48}
49
50impl FromStr for ReproductionMode {
51    type Err = ReproductionModeParseError;
52
53    fn from_str(value: &str) -> Result<Self, Self::Err> {
54        let trimmed = value.trim();
55
56        if trimmed.is_empty() {
57            return Err(ReproductionModeParseError::Empty);
58        }
59
60        match normalized_key(trimmed).as_str() {
61            "asexual" => Ok(Self::Asexual),
62            "sexual" => Ok(Self::Sexual),
63            "budding" => Ok(Self::Budding),
64            "fragmentation" => Ok(Self::Fragmentation),
65            "spore-formation" => Ok(Self::SporeFormation),
66            "binary-fission" => Ok(Self::BinaryFission),
67            "pollination" => Ok(Self::Pollination),
68            "unknown" => Ok(Self::Unknown),
69            _ => Ok(Self::Custom(trimmed.to_string())),
70        }
71    }
72}
73
74/// Error returned when parsing reproduction modes fails.
75#[derive(Clone, Copy, Debug, Eq, PartialEq)]
76pub enum ReproductionModeParseError {
77    /// The reproduction mode was empty after trimming whitespace.
78    Empty,
79}
80
81impl fmt::Display for ReproductionModeParseError {
82    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            Self::Empty => formatter.write_str("reproduction mode cannot be empty"),
85        }
86    }
87}
88
89impl Error for ReproductionModeParseError {}
90
91/// Fertilization mode vocabulary.
92#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
93pub enum FertilizationMode {
94    /// Internal fertilization.
95    Internal,
96    /// External fertilization.
97    External,
98    /// Self-fertilization.
99    SelfFertilization,
100    /// Cross-fertilization.
101    CrossFertilization,
102    /// Unknown fertilization mode.
103    Unknown,
104    /// Caller-defined fertilization mode text.
105    Custom(String),
106}
107
108impl fmt::Display for FertilizationMode {
109    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
110        match self {
111            Self::Internal => formatter.write_str("internal"),
112            Self::External => formatter.write_str("external"),
113            Self::SelfFertilization => formatter.write_str("self-fertilization"),
114            Self::CrossFertilization => formatter.write_str("cross-fertilization"),
115            Self::Unknown => formatter.write_str("unknown"),
116            Self::Custom(value) => formatter.write_str(value),
117        }
118    }
119}
120
121impl FromStr for FertilizationMode {
122    type Err = FertilizationModeParseError;
123
124    fn from_str(value: &str) -> Result<Self, Self::Err> {
125        let trimmed = value.trim();
126
127        if trimmed.is_empty() {
128            return Err(FertilizationModeParseError::Empty);
129        }
130
131        match normalized_key(trimmed).as_str() {
132            "internal" => Ok(Self::Internal),
133            "external" => Ok(Self::External),
134            "self-fertilization" | "selfing" => Ok(Self::SelfFertilization),
135            "cross-fertilization" => Ok(Self::CrossFertilization),
136            "unknown" => Ok(Self::Unknown),
137            _ => Ok(Self::Custom(trimmed.to_string())),
138        }
139    }
140}
141
142/// Error returned when parsing fertilization modes fails.
143#[derive(Clone, Copy, Debug, Eq, PartialEq)]
144pub enum FertilizationModeParseError {
145    /// The fertilization mode was empty after trimming whitespace.
146    Empty,
147}
148
149impl fmt::Display for FertilizationModeParseError {
150    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
151        match self {
152            Self::Empty => formatter.write_str("fertilization mode cannot be empty"),
153        }
154    }
155}
156
157impl Error for FertilizationModeParseError {}
158
159#[cfg(test)]
160mod tests {
161    use super::{
162        FertilizationMode, FertilizationModeParseError, ReproductionMode,
163        ReproductionModeParseError,
164    };
165
166    #[test]
167    fn displays_and_parses_reproduction_mode() -> Result<(), ReproductionModeParseError> {
168        assert_eq!(
169            ReproductionMode::BinaryFission.to_string(),
170            "binary-fission"
171        );
172        assert_eq!(
173            "spore formation".parse::<ReproductionMode>()?,
174            ReproductionMode::SporeFormation
175        );
176        assert_eq!(
177            "pollination".parse::<ReproductionMode>()?,
178            ReproductionMode::Pollination
179        );
180        Ok(())
181    }
182
183    #[test]
184    fn displays_and_parses_fertilization_mode() -> Result<(), FertilizationModeParseError> {
185        assert_eq!(
186            FertilizationMode::SelfFertilization.to_string(),
187            "self-fertilization"
188        );
189        assert_eq!(
190            "external".parse::<FertilizationMode>()?,
191            FertilizationMode::External
192        );
193        assert_eq!(
194            "selfing".parse::<FertilizationMode>()?,
195            FertilizationMode::SelfFertilization
196        );
197        Ok(())
198    }
199
200    #[test]
201    fn parses_custom_reproduction_mode() -> Result<(), ReproductionModeParseError> {
202        assert_eq!(
203            "vegetative propagation".parse::<ReproductionMode>()?,
204            ReproductionMode::Custom("vegetative propagation".to_string())
205        );
206        assert_eq!(
207            "".parse::<ReproductionMode>(),
208            Err(ReproductionModeParseError::Empty)
209        );
210        Ok(())
211    }
212
213    #[test]
214    fn parses_custom_fertilization_mode() -> Result<(), FertilizationModeParseError> {
215        assert_eq!(
216            "broadcast spawning".parse::<FertilizationMode>()?,
217            FertilizationMode::Custom("broadcast spawning".to_string())
218        );
219        assert_eq!(
220            " ".parse::<FertilizationMode>(),
221            Err(FertilizationModeParseError::Empty)
222        );
223        Ok(())
224    }
225}