ferro_hgvs/error_handling/
mod.rs1pub mod codes;
51pub mod corrections;
52mod preprocessor;
53pub mod registry;
54mod types;
55
56pub use codes::{CodeCategory, CodeInfo, ModeAction, ModeBehavior};
57pub use corrections::{
58 detect_accession_typo, detect_amino_acid_typo, detect_edit_type_typo, detect_missing_version,
59 detect_swapped_positions, detect_typos, find_closest_match, levenshtein_distance,
60 DetectedCorrection, FuzzyMatch, TypoSuggestion, TypoTokenType,
61};
62pub use preprocessor::{CorrectionWarning, InputPreprocessor, PreprocessResult};
63pub use registry::{get_code_info, list_all_codes, list_error_codes, list_warning_codes};
64pub use types::{ErrorMode, ErrorOverride, ErrorType, ResolvedAction};
65
66use std::collections::HashMap;
67
68#[derive(Debug, Clone)]
73pub struct ErrorConfig {
74 pub mode: ErrorMode,
76 pub overrides: HashMap<ErrorType, ErrorOverride>,
78}
79
80impl ErrorConfig {
81 pub fn new(mode: ErrorMode) -> Self {
83 Self {
84 mode,
85 overrides: HashMap::new(),
86 }
87 }
88
89 pub fn strict() -> Self {
93 Self::new(ErrorMode::Strict)
94 }
95
96 pub fn lenient() -> Self {
100 Self::new(ErrorMode::Lenient)
101 }
102
103 pub fn silent() -> Self {
107 Self::new(ErrorMode::Silent)
108 }
109
110 pub fn with_override(mut self, error_type: ErrorType, override_: ErrorOverride) -> Self {
122 self.overrides.insert(error_type, override_);
123 self
124 }
125
126 pub fn set_override(&mut self, error_type: ErrorType, override_: ErrorOverride) {
130 self.overrides.insert(error_type, override_);
131 }
132
133 pub fn remove_override(&mut self, error_type: ErrorType) {
135 self.overrides.remove(&error_type);
136 }
137
138 pub fn action_for(&self, error_type: ErrorType) -> ResolvedAction {
142 let override_ = self.overrides.get(&error_type).copied().unwrap_or_default();
143 override_.resolve(self.mode)
144 }
145
146 pub fn should_reject(&self, error_type: ErrorType) -> bool {
148 self.action_for(error_type).should_reject()
149 }
150
151 pub fn should_correct(&self, error_type: ErrorType) -> bool {
153 self.action_for(error_type).should_correct()
154 }
155
156 pub fn should_warn(&self, error_type: ErrorType) -> bool {
158 self.action_for(error_type).should_warn()
159 }
160
161 pub fn preprocessor(&self) -> InputPreprocessor {
163 InputPreprocessor::new(self.clone())
164 }
165}
166
167impl Default for ErrorConfig {
168 fn default() -> Self {
169 Self::strict()
170 }
171}
172
173#[derive(Debug, Clone)]
178pub struct ParseResultWithWarnings<T> {
179 pub result: T,
181 pub warnings: Vec<CorrectionWarning>,
183 pub original_input: String,
185 pub preprocessed_input: String,
187}
188
189impl<T> ParseResultWithWarnings<T> {
190 pub fn new(
192 result: T,
193 warnings: Vec<CorrectionWarning>,
194 original_input: String,
195 preprocessed_input: String,
196 ) -> Self {
197 Self {
198 result,
199 warnings,
200 original_input,
201 preprocessed_input,
202 }
203 }
204
205 pub fn without_warnings(result: T, input: String) -> Self {
207 Self {
208 result,
209 warnings: Vec::new(),
210 original_input: input.clone(),
211 preprocessed_input: input,
212 }
213 }
214
215 pub fn had_corrections(&self) -> bool {
217 self.original_input != self.preprocessed_input
218 }
219
220 pub fn has_warnings(&self) -> bool {
222 !self.warnings.is_empty()
223 }
224
225 pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> ParseResultWithWarnings<U> {
227 ParseResultWithWarnings {
228 result: f(self.result),
229 warnings: self.warnings,
230 original_input: self.original_input,
231 preprocessed_input: self.preprocessed_input,
232 }
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
242 fn test_error_config_default() {
243 let config = ErrorConfig::default();
244 assert_eq!(config.mode, ErrorMode::Strict);
245 assert!(config.overrides.is_empty());
246 }
247
248 #[test]
249 fn test_error_config_strict() {
250 let config = ErrorConfig::strict();
251 assert_eq!(config.mode, ErrorMode::Strict);
252 assert!(config.should_reject(ErrorType::WrongDashCharacter));
253 }
254
255 #[test]
256 fn test_error_config_lenient() {
257 let config = ErrorConfig::lenient();
258 assert_eq!(config.mode, ErrorMode::Lenient);
259 assert!(config.should_correct(ErrorType::WrongDashCharacter));
260 assert!(config.should_warn(ErrorType::WrongDashCharacter));
261 }
262
263 #[test]
264 fn test_error_config_silent() {
265 let config = ErrorConfig::silent();
266 assert_eq!(config.mode, ErrorMode::Silent);
267 assert!(config.should_correct(ErrorType::WrongDashCharacter));
268 assert!(!config.should_warn(ErrorType::WrongDashCharacter));
269 }
270
271 #[test]
272 fn test_error_config_with_override() {
273 let config = ErrorConfig::lenient()
274 .with_override(ErrorType::LowercaseAminoAcid, ErrorOverride::Reject);
275
276 assert!(config.should_reject(ErrorType::LowercaseAminoAcid));
278
279 assert!(config.should_correct(ErrorType::WrongDashCharacter));
281 }
282
283 #[test]
284 fn test_error_config_set_override() {
285 let mut config = ErrorConfig::strict();
286 config.set_override(ErrorType::WrongDashCharacter, ErrorOverride::SilentCorrect);
287 assert!(config.should_correct(ErrorType::WrongDashCharacter));
288 assert!(!config.should_warn(ErrorType::WrongDashCharacter));
289 }
290
291 #[test]
292 fn test_error_config_remove_override() {
293 let mut config = ErrorConfig::lenient()
294 .with_override(ErrorType::WrongDashCharacter, ErrorOverride::Reject);
295
296 assert!(config.should_reject(ErrorType::WrongDashCharacter));
298
299 config.remove_override(ErrorType::WrongDashCharacter);
301 assert!(config.should_correct(ErrorType::WrongDashCharacter));
302 }
303
304 #[test]
305 fn test_error_config_action_for() {
306 let config =
307 ErrorConfig::lenient().with_override(ErrorType::PositionZero, ErrorOverride::Reject);
308
309 assert_eq!(
310 config.action_for(ErrorType::WrongDashCharacter),
311 ResolvedAction::WarnCorrect
312 );
313 assert_eq!(
314 config.action_for(ErrorType::PositionZero),
315 ResolvedAction::Reject
316 );
317 }
318
319 #[test]
320 fn test_error_config_preprocessor() {
321 let config = ErrorConfig::lenient();
322 let preprocessor = config.preprocessor();
323
324 let result = preprocessor.preprocess("c.100\u{2013}200del");
325 assert!(result.success);
326 assert_eq!(result.preprocessed, "c.100-200del");
327 }
328
329 #[test]
331 fn test_parse_result_with_warnings_new() {
332 let result = ParseResultWithWarnings::new(
333 42,
334 vec![CorrectionWarning::new(
335 ErrorType::WrongDashCharacter,
336 "test",
337 None,
338 "",
339 "",
340 )],
341 "original".to_string(),
342 "preprocessed".to_string(),
343 );
344
345 assert_eq!(result.result, 42);
346 assert!(result.has_warnings());
347 assert!(result.had_corrections());
348 }
349
350 #[test]
351 fn test_parse_result_with_warnings_without_warnings() {
352 let result = ParseResultWithWarnings::without_warnings(42, "input".to_string());
353
354 assert_eq!(result.result, 42);
355 assert!(!result.has_warnings());
356 assert!(!result.had_corrections());
357 }
358
359 #[test]
360 fn test_parse_result_with_warnings_map() {
361 let result = ParseResultWithWarnings::without_warnings(42, "input".to_string());
362 let mapped = result.map(|x| x.to_string());
363
364 assert_eq!(mapped.result, "42");
365 }
366}