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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
13pub enum ReproductionMode {
14 Asexual,
16 Sexual,
18 Budding,
20 Fragmentation,
22 SporeFormation,
24 BinaryFission,
26 Pollination,
28 Unknown,
30 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
76pub enum ReproductionModeParseError {
77 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#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
93pub enum FertilizationMode {
94 Internal,
96 External,
98 SelfFertilization,
100 CrossFertilization,
102 Unknown,
104 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#[derive(Clone, Copy, Debug, Eq, PartialEq)]
144pub enum FertilizationModeParseError {
145 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}