ferro_hgvs/normalize/
config.rs1use crate::error_handling::{ErrorConfig, ErrorMode, ErrorOverride, ErrorType, ResolvedAction};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
8pub enum ShuffleDirection {
9 #[default]
11 ThreePrime,
12 FivePrime,
14}
15
16impl std::fmt::Display for ShuffleDirection {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 match self {
19 ShuffleDirection::ThreePrime => write!(f, "3prime"),
20 ShuffleDirection::FivePrime => write!(f, "5prime"),
21 }
22 }
23}
24
25impl std::str::FromStr for ShuffleDirection {
26 type Err = String;
27
28 fn from_str(s: &str) -> Result<Self, Self::Err> {
29 match s.to_lowercase().as_str() {
30 "3prime" | "3'" | "three_prime" => Ok(ShuffleDirection::ThreePrime),
31 "5prime" | "5'" | "five_prime" => Ok(ShuffleDirection::FivePrime),
32 _ => Err(format!("Invalid shuffle direction: {}", s)),
33 }
34 }
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct NormalizeConfig {
40 pub shuffle_direction: ShuffleDirection,
42
43 pub cross_boundaries: bool,
45
46 #[serde(skip)]
48 pub error_config: ErrorConfig,
49
50 pub window_size: u64,
52
53 pub prevent_overlap: bool,
58}
59
60impl Default for NormalizeConfig {
61 fn default() -> Self {
62 Self {
63 shuffle_direction: ShuffleDirection::ThreePrime,
64 cross_boundaries: false,
65 error_config: ErrorConfig::lenient(),
68 window_size: 100,
69 prevent_overlap: true,
70 }
71 }
72}
73
74impl PartialEq for NormalizeConfig {
75 fn eq(&self, other: &Self) -> bool {
76 self.shuffle_direction == other.shuffle_direction
77 && self.cross_boundaries == other.cross_boundaries
78 && self.window_size == other.window_size
79 && self.prevent_overlap == other.prevent_overlap
80 }
81}
82
83impl Eq for NormalizeConfig {}
84
85impl NormalizeConfig {
86 pub fn new() -> Self {
88 Self::default()
89 }
90
91 pub fn strict() -> Self {
93 Self {
94 error_config: ErrorConfig::strict(),
95 ..Default::default()
96 }
97 }
98
99 pub fn lenient() -> Self {
101 Self {
102 error_config: ErrorConfig::lenient(),
103 ..Default::default()
104 }
105 }
106
107 pub fn silent() -> Self {
109 Self {
110 error_config: ErrorConfig::silent(),
111 ..Default::default()
112 }
113 }
114
115 pub fn with_direction(mut self, direction: ShuffleDirection) -> Self {
117 self.shuffle_direction = direction;
118 self
119 }
120
121 pub fn allow_crossing_boundaries(mut self) -> Self {
123 self.cross_boundaries = true;
124 self
125 }
126
127 pub fn with_error_mode(mut self, mode: ErrorMode) -> Self {
129 self.error_config = ErrorConfig::new(mode);
130 self
131 }
132
133 pub fn with_error_override(mut self, error_type: ErrorType, action: ErrorOverride) -> Self {
135 self.error_config = self.error_config.with_override(error_type, action);
136 self
137 }
138
139 #[deprecated(
141 since = "0.2.0",
142 note = "Use with_error_mode(ErrorMode::Silent) instead"
143 )]
144 pub fn skip_validation(mut self) -> Self {
145 self.error_config = self
146 .error_config
147 .with_override(ErrorType::RefSeqMismatch, ErrorOverride::SilentCorrect);
148 self
149 }
150
151 pub fn with_overlap_prevention(mut self, prevent: bool) -> Self {
153 self.prevent_overlap = prevent;
154 self
155 }
156
157 pub fn ref_mismatch_action(&self) -> ResolvedAction {
159 self.error_config.action_for(ErrorType::RefSeqMismatch)
160 }
161
162 pub fn should_reject_ref_mismatch(&self) -> bool {
164 self.ref_mismatch_action().should_reject()
165 }
166
167 pub fn should_warn_ref_mismatch(&self) -> bool {
169 self.ref_mismatch_action().should_warn()
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_default_config() {
179 let config = NormalizeConfig::default();
180 assert_eq!(config.shuffle_direction, ShuffleDirection::ThreePrime);
181 assert!(!config.cross_boundaries);
182 assert!(!config.should_reject_ref_mismatch());
184 assert!(config.should_warn_ref_mismatch());
185 }
186
187 #[test]
188 fn test_strict_config() {
189 let config = NormalizeConfig::strict();
190 assert!(config.should_reject_ref_mismatch());
191 assert!(!config.should_warn_ref_mismatch());
192 }
193
194 #[test]
195 fn test_lenient_config() {
196 let config = NormalizeConfig::lenient();
197 assert!(!config.should_reject_ref_mismatch());
198 assert!(config.should_warn_ref_mismatch());
199 }
200
201 #[test]
202 fn test_silent_config() {
203 let config = NormalizeConfig::silent();
204 assert!(!config.should_reject_ref_mismatch());
205 assert!(!config.should_warn_ref_mismatch());
206 }
207
208 #[test]
209 fn test_error_override() {
210 let config = NormalizeConfig::lenient()
212 .with_error_override(ErrorType::RefSeqMismatch, ErrorOverride::Reject);
213 assert!(config.should_reject_ref_mismatch());
214 }
215
216 #[test]
217 fn test_direction_parsing() {
218 assert_eq!(
219 "3prime".parse::<ShuffleDirection>().unwrap(),
220 ShuffleDirection::ThreePrime
221 );
222 assert_eq!(
223 "5prime".parse::<ShuffleDirection>().unwrap(),
224 ShuffleDirection::FivePrime
225 );
226 }
227
228 #[test]
229 #[allow(deprecated)]
230 fn test_skip_validation_deprecated() {
231 let config = NormalizeConfig::default().skip_validation();
232 assert!(!config.should_reject_ref_mismatch());
234 assert!(!config.should_warn_ref_mismatch());
235 }
236}