omics_variation/
lib.rs

1//! Genomic variation.
2
3use std::str::FromStr;
4
5use omics_molecule::compound::Nucleotide;
6
7pub mod snv;
8
9/// An error related to a [`Variant`].
10#[derive(Debug)]
11pub enum Error {
12    /// Unsuccessfully attempted to parse a [`Variant`] from a string.
13    ParseError(String),
14}
15
16impl std::fmt::Display for Error {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        match self {
19            Error::ParseError(v) => write!(f, "unable to parse a variant from string: {v}"),
20        }
21    }
22}
23
24impl std::error::Error for Error {}
25
26/// A variant.
27#[derive(Debug)]
28pub enum Variant<N: Nucleotide> {
29    /// A single nucleotide substitution.
30    SingleNucleotideVariation(snv::Variant<N>),
31}
32
33impl<N: Nucleotide> std::str::FromStr for Variant<N>
34where
35    <N as FromStr>::Err: std::fmt::Debug + std::fmt::Display,
36{
37    type Err = Error;
38
39    fn from_str(s: &str) -> Result<Self, Self::Err> {
40        if let Ok(snv) = s.parse::<snv::Variant<N>>() {
41            return Ok(Variant::SingleNucleotideVariation(snv));
42        }
43
44        Err(Error::ParseError(s.to_string()))
45    }
46}
47
48impl<N: Nucleotide> std::fmt::Display for Variant<N>
49where
50    <N as FromStr>::Err: std::fmt::Debug + std::fmt::Display,
51{
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        match self {
54            Variant::SingleNucleotideVariation(variant) => write!(f, "{}", variant),
55        }
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use omics_coordinate::Strand;
62    use omics_molecule::polymer::dna;
63
64    use super::*;
65
66    #[test]
67    fn it_parses_dna_snvs_correctly() -> Result<(), Box<dyn std::error::Error>> {
68        let variant = "seq0:+:1:A:C".parse::<Variant<dna::Nucleotide>>()?;
69        assert!(matches!(variant, Variant::SingleNucleotideVariation(_)));
70
71        match variant {
72            Variant::SingleNucleotideVariation(snv) => {
73                assert_eq!(snv.coordinate().contig().as_str(), "seq0");
74                assert_eq!(snv.coordinate().strand(), Strand::Positive);
75                assert_eq!(snv.coordinate().position().get(), 1);
76                assert_eq!(snv.reference(), &dna::Nucleotide::A);
77                assert_eq!(snv.alternate(), &dna::Nucleotide::C);
78            }
79        }
80
81        Ok(())
82    }
83
84    #[test]
85    fn it_errors_when_attempting_to_parse_invalid_variants()
86    -> Result<(), Box<dyn std::error::Error>> {
87        let err = "seq0:1:A".parse::<Variant<dna::Nucleotide>>().unwrap_err();
88        assert_eq!(
89            err.to_string(),
90            "unable to parse a variant from string: seq0:1:A"
91        );
92
93        let err = "seq0:1:A:".parse::<Variant<dna::Nucleotide>>().unwrap_err();
94        assert_eq!(
95            err.to_string(),
96            "unable to parse a variant from string: seq0:1:A:"
97        );
98
99        let err = "seq0:A:C:1"
100            .parse::<Variant<dna::Nucleotide>>()
101            .unwrap_err();
102        assert_eq!(
103            err.to_string(),
104            "unable to parse a variant from string: seq0:A:C:1"
105        );
106
107        Ok(())
108    }
109
110    #[test]
111    fn it_serializes_correctly() -> Result<(), Box<dyn std::error::Error>> {
112        let variant = "seq0:+:1:A:C".parse::<Variant<dna::Nucleotide>>()?;
113        assert_eq!(variant.to_string(), "seq0:+:1:A:C");
114
115        let variant = "seq0:+:1:A:C".parse::<Variant<dna::Nucleotide>>()?;
116        assert_eq!(variant.to_string(), "seq0:+:1:A:C");
117
118        Ok(())
119    }
120}