1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::{fmt, str::FromStr};
5use std::error::Error;
6
7fn non_empty_text(value: impl AsRef<str>) -> Result<String, GeneValueError> {
8 let trimmed = value.as_ref().trim();
9
10 if trimmed.is_empty() {
11 Err(GeneValueError::Empty)
12 } else {
13 Ok(trimmed.to_string())
14 }
15}
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum GeneValueError {
20 Empty,
22}
23
24impl fmt::Display for GeneValueError {
25 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 Self::Empty => formatter.write_str("gene value cannot be empty"),
28 }
29 }
30}
31
32impl Error for GeneValueError {}
33
34#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
36pub struct GeneId(String);
37
38impl GeneId {
39 pub fn new(value: impl AsRef<str>) -> Result<Self, GeneValueError> {
45 non_empty_text(value).map(Self)
46 }
47
48 #[must_use]
50 pub fn as_str(&self) -> &str {
51 &self.0
52 }
53
54 #[must_use]
56 pub fn into_string(self) -> String {
57 self.0
58 }
59}
60
61impl AsRef<str> for GeneId {
62 fn as_ref(&self) -> &str {
63 self.as_str()
64 }
65}
66
67impl fmt::Display for GeneId {
68 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
69 formatter.write_str(self.as_str())
70 }
71}
72
73impl FromStr for GeneId {
74 type Err = GeneValueError;
75
76 fn from_str(value: &str) -> Result<Self, Self::Err> {
77 Self::new(value)
78 }
79}
80
81#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
83pub struct GeneSymbol(String);
84
85impl GeneSymbol {
86 pub fn new(value: impl AsRef<str>) -> Result<Self, GeneValueError> {
92 non_empty_text(value).map(Self)
93 }
94
95 #[must_use]
97 pub fn as_str(&self) -> &str {
98 &self.0
99 }
100
101 #[must_use]
103 pub fn into_string(self) -> String {
104 self.0
105 }
106}
107
108impl AsRef<str> for GeneSymbol {
109 fn as_ref(&self) -> &str {
110 self.as_str()
111 }
112}
113
114impl fmt::Display for GeneSymbol {
115 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
116 formatter.write_str(self.as_str())
117 }
118}
119
120impl FromStr for GeneSymbol {
121 type Err = GeneValueError;
122
123 fn from_str(value: &str) -> Result<Self, Self::Err> {
124 Self::new(value)
125 }
126}
127
128#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
130pub struct GeneName(String);
131
132impl GeneName {
133 pub fn new(value: impl AsRef<str>) -> Result<Self, GeneValueError> {
139 non_empty_text(value).map(Self)
140 }
141
142 #[must_use]
144 pub fn as_str(&self) -> &str {
145 &self.0
146 }
147
148 #[must_use]
150 pub fn into_string(self) -> String {
151 self.0
152 }
153}
154
155impl AsRef<str> for GeneName {
156 fn as_ref(&self) -> &str {
157 self.as_str()
158 }
159}
160
161impl fmt::Display for GeneName {
162 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
163 formatter.write_str(self.as_str())
164 }
165}
166
167impl FromStr for GeneName {
168 type Err = GeneValueError;
169
170 fn from_str(value: &str) -> Result<Self, Self::Err> {
171 Self::new(value)
172 }
173}
174
175#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
177pub struct Locus(String);
178
179impl Locus {
180 pub fn new(value: impl AsRef<str>) -> Result<Self, GeneValueError> {
186 non_empty_text(value).map(Self)
187 }
188
189 #[must_use]
191 pub fn as_str(&self) -> &str {
192 &self.0
193 }
194
195 #[must_use]
197 pub fn into_string(self) -> String {
198 self.0
199 }
200}
201
202impl AsRef<str> for Locus {
203 fn as_ref(&self) -> &str {
204 self.as_str()
205 }
206}
207
208impl fmt::Display for Locus {
209 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
210 formatter.write_str(self.as_str())
211 }
212}
213
214impl FromStr for Locus {
215 type Err = GeneValueError;
216
217 fn from_str(value: &str) -> Result<Self, Self::Err> {
218 Self::new(value)
219 }
220}
221
222#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
224pub struct Allele(String);
225
226impl Allele {
227 pub fn new(value: impl AsRef<str>) -> Result<Self, GeneValueError> {
233 non_empty_text(value).map(Self)
234 }
235
236 #[must_use]
238 pub fn as_str(&self) -> &str {
239 &self.0
240 }
241
242 #[must_use]
244 pub fn into_string(self) -> String {
245 self.0
246 }
247}
248
249impl AsRef<str> for Allele {
250 fn as_ref(&self) -> &str {
251 self.as_str()
252 }
253}
254
255impl fmt::Display for Allele {
256 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
257 formatter.write_str(self.as_str())
258 }
259}
260
261impl FromStr for Allele {
262 type Err = GeneValueError;
263
264 fn from_str(value: &str) -> Result<Self, Self::Err> {
265 Self::new(value)
266 }
267}
268
269#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
271pub struct Genotype {
272 alleles: Vec<Allele>,
273}
274
275impl Genotype {
276 #[must_use]
278 pub const fn new(alleles: Vec<Allele>) -> Self {
279 Self { alleles }
280 }
281
282 #[must_use]
284 pub fn alleles(&self) -> &[Allele] {
285 &self.alleles
286 }
287
288 #[must_use]
290 pub const fn len(&self) -> usize {
291 self.alleles.len()
292 }
293
294 #[must_use]
296 pub const fn is_empty(&self) -> bool {
297 self.alleles.is_empty()
298 }
299}
300
301impl From<Vec<Allele>> for Genotype {
302 fn from(alleles: Vec<Allele>) -> Self {
303 Self::new(alleles)
304 }
305}
306
307impl fmt::Display for Genotype {
308 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
309 for (index, allele) in self.alleles.iter().enumerate() {
310 if index > 0 {
311 formatter.write_str("/")?;
312 }
313 write!(formatter, "{allele}")?;
314 }
315 Ok(())
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::{Allele, GeneSymbol, GeneValueError, Genotype, Locus};
322
323 #[test]
324 fn constructs_valid_gene_symbol() -> Result<(), GeneValueError> {
325 let symbol = GeneSymbol::new("BRCA1")?;
326
327 assert_eq!(symbol.as_str(), "BRCA1");
328 assert_eq!(symbol.to_string(), "BRCA1");
329 Ok(())
330 }
331
332 #[test]
333 fn rejects_empty_gene_symbol() {
334 assert_eq!(GeneSymbol::new(" "), Err(GeneValueError::Empty));
335 }
336
337 #[test]
338 fn constructs_valid_locus() -> Result<(), GeneValueError> {
339 let locus = Locus::new("17q21.31")?;
340
341 assert_eq!(locus.to_string(), "17q21.31");
342 Ok(())
343 }
344
345 #[test]
346 fn constructs_valid_allele() -> Result<(), GeneValueError> {
347 let allele = Allele::new("A")?;
348
349 assert_eq!(allele.as_str(), "A");
350 Ok(())
351 }
352
353 #[test]
354 fn constructs_genotype() -> Result<(), GeneValueError> {
355 let genotype = Genotype::new(vec![Allele::new("A")?, Allele::new("a")?]);
356
357 assert_eq!(genotype.len(), 2);
358 assert_eq!(genotype.alleles()[0].as_str(), "A");
359 assert!(!genotype.is_empty());
360 Ok(())
361 }
362
363 #[test]
364 fn displays_genotype() -> Result<(), GeneValueError> {
365 let genotype = Genotype::new(vec![Allele::new("A")?, Allele::new("a")?]);
366
367 assert_eq!(genotype.to_string(), "A/a");
368 Ok(())
369 }
370}