Skip to main content

gen_core/
strand.rs

1use std::fmt;
2
3use noodles::gff::feature::record::Strand as GFFStrand;
4use rusqlite::{
5    ToSql,
6    types::{FromSql, FromSqlResult, ToSqlOutput, Value, ValueRef},
7};
8use serde::{Deserialize, Serialize};
9
10use crate::{errors::StrandError, gen_core_capnp};
11
12#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Ord, PartialOrd)]
13pub enum Strand {
14    Forward,
15    Reverse,
16    Unknown,
17    ImportantButUnknown,
18}
19
20impl Strand {
21    pub fn is_ambiguous(a: Strand) -> bool {
22        a == Strand::ImportantButUnknown || a == Strand::Unknown
23    }
24
25    pub fn is_compatible(a: Strand, b: Strand) -> bool {
26        let a_ambig = Strand::is_ambiguous(a);
27        let b_ambig = Strand::is_ambiguous(b);
28        (a_ambig || b_ambig) || a == b
29    }
30}
31
32// example https://docs.rs/rusqlite/latest/rusqlite/types/index.html
33impl ToSql for Strand {
34    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
35        let result = match self {
36            Strand::Forward => "+".into(),
37            Strand::Reverse => "-".into(),
38            Strand::Unknown => ".".into(),
39            Strand::ImportantButUnknown => "?".into(),
40        };
41        Ok(result)
42    }
43}
44
45impl From<Strand> for Value {
46    fn from(value: Strand) -> Value {
47        let result = match value {
48            Strand::Forward => "+",
49            Strand::Reverse => "-",
50            Strand::Unknown => ".",
51            Strand::ImportantButUnknown => "?",
52        };
53        Value::Text(result.to_string())
54    }
55}
56
57impl From<GFFStrand> for Strand {
58    fn from(value: GFFStrand) -> Strand {
59        match value {
60            GFFStrand::Forward => Strand::Forward,
61            GFFStrand::Unknown => Strand::Unknown,
62            GFFStrand::Reverse => Strand::Reverse,
63            GFFStrand::None => Strand::Unknown,
64        }
65    }
66}
67
68impl From<Strand> for GFFStrand {
69    fn from(value: Strand) -> GFFStrand {
70        match value {
71            Strand::Forward => GFFStrand::Forward,
72            Strand::Unknown => GFFStrand::Unknown,
73            Strand::Reverse => GFFStrand::Reverse,
74            Strand::ImportantButUnknown => GFFStrand::None,
75        }
76    }
77}
78
79impl FromSql for Strand {
80    fn column_result(value: ValueRef) -> FromSqlResult<Self> {
81        let result = match value.as_str() {
82            Ok("+") => Strand::Forward,
83            Ok("-") => Strand::Reverse,
84            Ok(".") => Strand::Unknown,
85            Ok("?") => Strand::ImportantButUnknown,
86            _ => panic!("Invalid entry in database"),
87        };
88        Ok(result)
89    }
90}
91
92impl TryFrom<&str> for Strand {
93    type Error = StrandError;
94
95    fn try_from(value: &str) -> Result<Self, Self::Error> {
96        match value {
97            "+" => Ok(Self::Forward),
98            "-" => Ok(Self::Reverse),
99            "." => Ok(Self::Unknown),
100            "?" => Ok(Self::ImportantButUnknown),
101            v => Err(StrandError::InvalidStrand(format!(
102                "Invalid strand entry: {v}"
103            ))),
104        }
105    }
106}
107
108impl From<Strand> for gen_core_capnp::Strand {
109    fn from(value: Strand) -> gen_core_capnp::Strand {
110        match value {
111            Strand::Forward => gen_core_capnp::Strand::Forward,
112            Strand::Unknown => gen_core_capnp::Strand::Unknown,
113            Strand::Reverse => gen_core_capnp::Strand::Reverse,
114            Strand::ImportantButUnknown => gen_core_capnp::Strand::ImportantButUnknown,
115        }
116    }
117}
118
119impl From<gen_core_capnp::Strand> for Strand {
120    fn from(value: gen_core_capnp::Strand) -> Strand {
121        match value {
122            gen_core_capnp::Strand::Forward => Strand::Forward,
123            gen_core_capnp::Strand::Unknown => Strand::Unknown,
124            gen_core_capnp::Strand::Reverse => Strand::Reverse,
125            gen_core_capnp::Strand::ImportantButUnknown => Strand::ImportantButUnknown,
126        }
127    }
128}
129
130impl fmt::Display for Strand {
131    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
132        let result = match self {
133            Strand::Forward => "+",
134            Strand::Reverse => "-",
135            Strand::Unknown => ".",
136            Strand::ImportantButUnknown => "?",
137        };
138        formatter.write_str(result)
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145
146    #[test]
147    fn test_formats() {
148        assert_eq!(format!("{v}", v = Strand::Forward), "+");
149        assert_eq!(format!("{v}", v = Strand::Reverse), "-");
150        assert_eq!(format!("{v}", v = Strand::ImportantButUnknown), "?");
151        assert_eq!(format!("{v}", v = Strand::Unknown), ".");
152    }
153
154    #[test]
155    fn test_column_conversion() {
156        assert_eq!(
157            Strand::column_result(ValueRef::Text("+".as_bytes())).unwrap(),
158            Strand::Forward
159        );
160        assert_eq!(
161            Strand::column_result(ValueRef::Text("-".as_bytes())).unwrap(),
162            Strand::Reverse
163        );
164        assert_eq!(
165            Strand::column_result(ValueRef::Text(".".as_bytes())).unwrap(),
166            Strand::Unknown
167        );
168        assert_eq!(
169            Strand::column_result(ValueRef::Text("?".as_bytes())).unwrap(),
170            Strand::ImportantButUnknown
171        );
172    }
173}