From f3a21022d924f97874804aa5b687472f17ae01d8 Mon Sep 17 00:00:00 2001 From: jeff-k Date: Fri, 31 May 2024 19:22:25 +0100 Subject: [PATCH] improve error messages for translation module --- bio-seq/src/translation/mod.rs | 69 +++++++++++++++++++++-------- bio-seq/src/translation/standard.rs | 47 +++++++++++--------- 2 files changed, 77 insertions(+), 39 deletions(-) diff --git a/bio-seq/src/translation/mod.rs b/bio-seq/src/translation/mod.rs index 9606cd4..e64f672 100644 --- a/bio-seq/src/translation/mod.rs +++ b/bio-seq/src/translation/mod.rs @@ -6,27 +6,55 @@ //! //! ## Errors //! -use core::fmt::Display; +use core::fmt; use std::collections::HashMap; use crate::codec::Codec; -use crate::error::TranslationError; -use crate::prelude::{Seq, SeqSlice}; +use crate::prelude::{Amino, Dna, Seq, SeqSlice}; mod standard; pub use crate::translation::standard::STANDARD; +/// Error conditions for codon/amino acid translation +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum TranslationError { + AmbiguousCodon(B), + AmbiguousTranslation(Seq), + InvalidCodon(Seq), + InvalidAmino(B), +} + +impl fmt::Display for TranslationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TranslationError::AmbiguousCodon(amino) => { + write!(f, "Multiple codon sequences: {}", amino) + } + TranslationError::AmbiguousTranslation(codon) => { + write!(f, "Ambiguous translations for codon: {}", codon) + } + TranslationError::InvalidCodon(codon) => write!(f, "Invalid codon sequence: {}", codon), + TranslationError::InvalidAmino(amino) => { + write!(f, "Invalid amino acid character: {:?}", amino) + } + } + } +} + +// #![feature(error_in_core) +impl std::error::Error for TranslationError {} + /// A codon translation table where all codons map to amino acids -pub trait TranslationTable { +pub trait TranslationTable { fn to_amino(&self, codon: &SeqSlice) -> B; - fn to_codon(&self, amino: B) -> Result, TranslationError>; + fn to_codon(&self, amino: B) -> Result, TranslationError>; } /// A partial translation table where not all triples of characters map to amino acids -pub trait PartialTranslationTable { - fn try_to_amino(&self, codon: &SeqSlice) -> Result; - fn try_to_codon(&self, amino: B) -> Result, TranslationError>; +pub trait PartialTranslationTable { + fn try_to_amino(&self, codon: &SeqSlice) -> Result>; + fn try_to_codon(&self, amino: B) -> Result, TranslationError>; } /// A customisable translation table @@ -36,7 +64,7 @@ pub struct CodonTable { inverse_table: HashMap>>, } -impl CodonTable { +impl CodonTable { pub fn from_map(table: T) -> Self where T: Into, B>>, @@ -57,19 +85,22 @@ impl CodonTable { } } -impl PartialTranslationTable for CodonTable { - fn try_to_amino(&self, codon: &SeqSlice) -> Result { +impl PartialTranslationTable for CodonTable { + fn try_to_amino(&self, codon: &SeqSlice) -> Result> { match self.table.get(&Seq::from(codon)) { Some(amino) => Ok(*amino), - None => Err(TranslationError::InvalidCodon), + None => Err(TranslationError::InvalidCodon(Seq::from(codon))), } } - fn try_to_codon(&self, amino: B) -> Result, TranslationError> { - match self.inverse_table.get(&amino) { - Some(Some(codon)) => Ok(codon.clone()), - Some(None) => Err(TranslationError::AmbiguousCodon), - None => Err(TranslationError::InvalidCodon), + fn try_to_codon(&self, amino: B) -> Result, TranslationError> { + if let Some(codon) = self.inverse_table.get(&amino) { + match codon { + Some(codon) => Ok(codon.clone()), + None => Err(TranslationError::AmbiguousCodon(amino)), + } + } else { + Err(TranslationError::InvalidAmino(amino)) } } } @@ -105,11 +136,11 @@ mod tests { assert_eq!(table.try_to_codon(Amino::C), Ok(dna!("CCC"))); assert_eq!( table.try_to_codon(Amino::A), - Err(TranslationError::AmbiguousCodon) + Err(TranslationError::AmbiguousCodon(Amino::A)) ); assert_eq!( table.try_to_codon(Amino::X), - Err(TranslationError::InvalidCodon) + Err(TranslationError::InvalidAmino(Amino::X)) ); } diff --git a/bio-seq/src/translation/standard.rs b/bio-seq/src/translation/standard.rs index 83bbc6a..33b8a13 100644 --- a/bio-seq/src/translation/standard.rs +++ b/bio-seq/src/translation/standard.rs @@ -72,15 +72,18 @@ impl TranslationTable for Standard { } /// There are no unambiguous translations from amino acid to DNA codon except M and W - fn to_codon(&self, _amino: Amino) -> Result, TranslationError> { - Err(TranslationError::AmbiguousCodon) + fn to_codon(&self, amino: Amino) -> Result, TranslationError> { + Err(TranslationError::AmbiguousCodon(amino)) } } impl PartialTranslationTable for Standard { - fn try_to_amino(&self, codon: &SeqSlice) -> Result { + fn try_to_amino( + &self, + codon: &SeqSlice, + ) -> Result> { if codon.len() != 3 { - return Err(TranslationError::InvalidCodon); + return Err(TranslationError::InvalidCodon(codon.into())); } for (iupac_set, amino) in IUPAC_TO_AMINO.get_or_init(initialise_iupac_to_amino) { if iupac_set.contains(codon) { @@ -88,16 +91,16 @@ impl PartialTranslationTable for Standard { } } - Err(TranslationError::AmbiguousCodon) + Err(TranslationError::AmbiguousTranslation(codon.into())) } - fn try_to_codon(&self, amino: Amino) -> Result, TranslationError> { + fn try_to_codon(&self, amino: Amino) -> Result, TranslationError> { let amino_to_iupac = AMINO_TO_IUPAC.get_or_init(initialise_amino_to_iupac); match amino_to_iupac.get(&amino) { - Some(None) => Err(TranslationError::AmbiguousCodon), + Some(None) => Err(TranslationError::AmbiguousCodon(amino)), Some(Some(codon)) => Ok(codon.clone()), - None => Err(TranslationError::AmbiguousCodon), + None => Err(TranslationError::AmbiguousCodon(amino)), } } } @@ -171,7 +174,7 @@ mod tests { for amino in &seq { match STANDARD.try_to_codon(amino) { Ok(codon) => iupacs.extend(&codon), - Err(TranslationError::AmbiguousCodon) => ambs.push(amino), + Err(TranslationError::AmbiguousCodon(amino)) => ambs.push(amino), _ => panic!(), } } @@ -196,16 +199,20 @@ mod tests { fn iupac_to_amino_errors() { assert_eq!( STANDARD.try_to_amino(&iupac!("TN")), - Err(TranslationError::InvalidCodon) + Err(TranslationError::InvalidCodon( + Seq::::try_from("TN").unwrap() + )) ); assert_eq!( STANDARD.try_to_amino(&iupac!("NYTN")), - Err(TranslationError::InvalidCodon) + Err(TranslationError::InvalidCodon("NYTN".try_into().unwrap())) ); assert_eq!( STANDARD.try_to_amino(&iupac!("YTN")), - Err(TranslationError::AmbiguousCodon) + Err(TranslationError::AmbiguousTranslation( + "YTN".try_into().unwrap() + )) ); } @@ -213,36 +220,36 @@ mod tests { fn ambiguous_amino_to_iupac() { assert_eq!( STANDARD.try_to_codon(Amino::L), - Err(TranslationError::AmbiguousCodon) + Err(TranslationError::AmbiguousCodon(Amino::L)) ); assert_eq!( STANDARD.try_to_codon(Amino::S), - Err(TranslationError::AmbiguousCodon) + Err(TranslationError::AmbiguousCodon(Amino::S)) ); assert_eq!( STANDARD.try_to_codon(Amino::R), - Err(TranslationError::AmbiguousCodon) + Err(TranslationError::AmbiguousCodon(Amino::R)) ); assert_eq!( STANDARD.try_to_codon(Amino::X), - Err(TranslationError::AmbiguousCodon) + Err(TranslationError::AmbiguousCodon(Amino::X)) ); assert_ne!( STANDARD.try_to_codon(Amino::A), - Err(TranslationError::AmbiguousCodon) + Err(TranslationError::AmbiguousCodon(Amino::A)) ); assert_ne!( STANDARD.try_to_codon(Amino::N), - Err(TranslationError::AmbiguousCodon) + Err(TranslationError::AmbiguousCodon(Amino::N)) ); assert_ne!( STANDARD.try_to_codon(Amino::W), - Err(TranslationError::AmbiguousCodon) + Err(TranslationError::AmbiguousCodon(Amino::W)) ); assert_ne!( STANDARD.try_to_codon(Amino::M), - Err(TranslationError::AmbiguousCodon) + Err(TranslationError::AmbiguousCodon(Amino::M)) ); } }