Skip to content

Commit

Permalink
improve error messages for translation module
Browse files Browse the repository at this point in the history
  • Loading branch information
jeff-k committed May 31, 2024
1 parent 2095362 commit f3a2102
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 39 deletions.
69 changes: 50 additions & 19 deletions bio-seq/src/translation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<A: Codec = Dna, B: Codec + fmt::Display + fmt::Debug = Amino> {
AmbiguousCodon(B),
AmbiguousTranslation(Seq<A>),
InvalidCodon(Seq<A>),
InvalidAmino(B),
}

impl<A: Codec, B: Codec + fmt::Display> fmt::Display for TranslationError<A, B> {
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<A: Codec, B: Codec + fmt::Display + fmt::Debug> std::error::Error for TranslationError<A, B> {}

/// A codon translation table where all codons map to amino acids
pub trait TranslationTable<A: Codec, B: Codec> {
pub trait TranslationTable<A: Codec, B: Codec + fmt::Display> {
fn to_amino(&self, codon: &SeqSlice<A>) -> B;
fn to_codon(&self, amino: B) -> Result<Seq<A>, TranslationError>;
fn to_codon(&self, amino: B) -> Result<Seq<A>, TranslationError<A, B>>;
}

/// A partial translation table where not all triples of characters map to amino acids
pub trait PartialTranslationTable<A: Codec, B: Codec> {
fn try_to_amino(&self, codon: &SeqSlice<A>) -> Result<B, TranslationError>;
fn try_to_codon(&self, amino: B) -> Result<Seq<A>, TranslationError>;
pub trait PartialTranslationTable<A: Codec, B: Codec + fmt::Display> {
fn try_to_amino(&self, codon: &SeqSlice<A>) -> Result<B, TranslationError<A, B>>;
fn try_to_codon(&self, amino: B) -> Result<Seq<A>, TranslationError<A, B>>;
}

/// A customisable translation table
Expand All @@ -36,7 +64,7 @@ pub struct CodonTable<A: Codec, B: Codec> {
inverse_table: HashMap<B, Option<Seq<A>>>,
}

impl<A: Codec, B: Codec + Display> CodonTable<A, B> {
impl<A: Codec, B: Codec + fmt::Display> CodonTable<A, B> {
pub fn from_map<T>(table: T) -> Self
where
T: Into<HashMap<Seq<A>, B>>,
Expand All @@ -57,19 +85,22 @@ impl<A: Codec, B: Codec + Display> CodonTable<A, B> {
}
}

impl<A: Codec, B: Codec + Display> PartialTranslationTable<A, B> for CodonTable<A, B> {
fn try_to_amino(&self, codon: &SeqSlice<A>) -> Result<B, TranslationError> {
impl<A: Codec, B: Codec + fmt::Display> PartialTranslationTable<A, B> for CodonTable<A, B> {
fn try_to_amino(&self, codon: &SeqSlice<A>) -> Result<B, TranslationError<A, B>> {
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<Seq<A>, 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<Seq<A>, TranslationError<A, B>> {
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))
}
}
}
Expand Down Expand Up @@ -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))
);
}

Expand Down
47 changes: 27 additions & 20 deletions bio-seq/src/translation/standard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,32 +72,35 @@ impl TranslationTable<Dna, Amino> for Standard {
}

/// There are no unambiguous translations from amino acid to DNA codon except M and W
fn to_codon(&self, _amino: Amino) -> Result<Seq<Dna>, TranslationError> {
Err(TranslationError::AmbiguousCodon)
fn to_codon(&self, amino: Amino) -> Result<Seq<Dna>, TranslationError<Dna, Amino>> {
Err(TranslationError::AmbiguousCodon(amino))
}
}

impl PartialTranslationTable<Iupac, Amino> for Standard {
fn try_to_amino(&self, codon: &SeqSlice<Iupac>) -> Result<Amino, TranslationError> {
fn try_to_amino(
&self,
codon: &SeqSlice<Iupac>,
) -> Result<Amino, TranslationError<Iupac, Amino>> {
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) {
return Ok(*amino);
}
}

Err(TranslationError::AmbiguousCodon)
Err(TranslationError::AmbiguousTranslation(codon.into()))
}

fn try_to_codon(&self, amino: Amino) -> Result<Seq<Iupac>, TranslationError> {
fn try_to_codon(&self, amino: Amino) -> Result<Seq<Iupac>, TranslationError<Iupac, Amino>> {
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)),
}
}
}
Expand Down Expand Up @@ -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!(),
}
}
Expand All @@ -196,53 +199,57 @@ mod tests {
fn iupac_to_amino_errors() {
assert_eq!(
STANDARD.try_to_amino(&iupac!("TN")),
Err(TranslationError::InvalidCodon)
Err(TranslationError::InvalidCodon(
Seq::<Iupac>::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()
))
);
}

#[test]
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))
);
}
}

0 comments on commit f3a2102

Please sign in to comment.