epimetheus_methylome/
modtype.rs

1use anyhow::{Result, bail};
2use std::{fmt, str::FromStr};
3
4/// Represents a DNA base modification type.
5///
6/// This enum defines the types of modifications that can occur on DNA bases,
7/// including their associated codes for parsing and visualization.
8///
9/// # Variants
10/// - `SixMA`: N6-methyladenine (6mA), represented by the pileup code `a`.
11/// - `FiveMC`: 5-methylcytosine (5mC), represented by the pileup code `m`.
12/// - `FourMC`: 4-methylcytosine (4mC), represented by the pileup code `21839`.
13///
14/// # Examples
15/// ```
16/// use epimetheus_methylome::ModType;
17///
18/// let mod_type = ModType::SixMA;
19/// assert_eq!(mod_type.to_pileup_code(), "a");
20/// ```
21#[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, PartialOrd, Ord)]
22pub enum ModType {
23    SixMA,
24    FiveMC,
25    FourMC,
26}
27
28impl ModType {
29    /// Returns the pileup code corresponding to the modification type.
30    ///
31    /// Pileup codes are compact representations of modification types used
32    /// in sequencing data (or maybe just modkit):
33    /// - `SixMA` (6mA): `"a"`
34    /// - `FiveMC` (5mC): `"m"`
35    /// - `FourMC` (4mC): `"21839"`
36    ///
37    /// # Examples
38    /// ```
39    /// use epimetheus_methylome::ModType;
40    ///
41    /// let mod_type = ModType::FiveMC;
42    /// assert_eq!(mod_type.to_pileup_code(), "m");
43    /// ```
44    pub fn to_pileup_code(&self) -> &'static str {
45        match self {
46            ModType::SixMA => "a",
47            ModType::FiveMC => "m",
48            ModType::FourMC => "21839",
49        }
50    }
51
52    /// Passes the sam header tag to ModType
53    ///
54    /// Sam tags have the following and more for the methylation types:
55    /// MM:Z:{*}
56    /// - `A+a` (6mA): `"a"`
57    /// - `C+m` (5mC): `"m"`
58    /// - `C+21839` (4mC): `"21839"`
59    ///
60    /// # Examples
61    /// ```
62    /// use epimetheus_methylome::ModType;
63    ///
64    /// assert_eq!(ModType::from_sam_code('A', "a"), Some(ModType::SixMA));
65    /// ```
66    pub fn from_sam_code(base: char, modification: &str) -> Option<Self> {
67        // WARN I should add the other bases in the future
68        match (base, modification) {
69            ('A', "a") => Some(ModType::SixMA),
70            ('C', "m") => Some(ModType::FiveMC),
71            ('C', "21839") => Some(ModType::FourMC),
72            _ => None,
73        }
74    }
75}
76
77impl fmt::Display for ModType {
78    /// Formats the modification type for display purposes.
79    ///
80    /// Each modification type is represented in the format:
81    /// `<Modification Name> (<Pileup Code>)`.
82    ///
83    /// For example:
84    /// - `6mA (a)` for `SixMA`
85    /// - `5mC (m)` for `FiveMC`
86    /// - `4mC (21839)` for `FourMC`
87    ///
88    /// # Examples
89    /// ```
90    /// use epimetheus_methylome::ModType;
91    ///
92    /// let mod_type = ModType::FourMC;
93    /// assert_eq!(format!("{}", mod_type), "4mC (21839)");
94    /// ```
95    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
96        match self {
97            ModType::SixMA => write!(f, "6mA (a)"),
98            ModType::FiveMC => write!(f, "5mC (m)"),
99            ModType::FourMC => write!(f, "4mC (21839)"),
100        }
101    }
102}
103
104/// Parses a modification type from a string.
105///
106/// The input string must match one of the following:
107/// - `"a"` for `SixMA` (6mA)
108/// - `"m"` for `FiveMC` (5mC)
109/// - `"21839"` for `FourMC` (4mC)
110///
111/// # Arguments
112/// - `mod_type`: A string slice representing the modification type.
113///
114/// # Returns
115/// - `Ok(ModType)` if the string matches a supported modification type.
116/// - `Err` if the string does not match any supported modification type.
117///
118/// # Examples
119/// ```
120/// use epimetheus_methylome::ModType;
121///
122/// let mod_type = "a".parse::<ModType>().unwrap();
123/// assert_eq!(mod_type, ModType::SixMA);
124///
125/// let invalid = "unsupported".parse::<ModType>();
126/// assert!(invalid.is_err());
127/// ```
128impl FromStr for ModType {
129    type Err = anyhow::Error;
130
131    fn from_str(s: &str) -> Result<Self, Self::Err> {
132        match s {
133            "a" => Ok(ModType::SixMA),
134            "m" => Ok(ModType::FiveMC),
135            "21839" => Ok(ModType::FourMC),
136            _ => bail!("Unsupported mod type: {}", s),
137        }
138    }
139}