use std::cmp;
use crate::models::transcript::Transcript;
use crate::models::Frame;
use crate::models::Strand;
use crate::utils::errors::BuildCodonError;
use crate::utils::intersect;
#[derive(Debug)]
pub struct Codon {
fragments: Vec<CodonFragment>,
}
impl Codon {
pub fn new(fragments: Vec<CodonFragment>) -> Result<Self, BuildCodonError> {
let len: u32 = fragments.iter().fold(0, |sum, fgt| sum + fgt.len());
if len != 3 {
return Err(BuildCodonError::new("length != 3"));
}
Ok(Self { fragments })
}
pub fn from_transcript(transcript: &Transcript, start: &u32) -> Result<Self, BuildCodonError> {
match transcript.strand() {
Strand::Plus => Codon::downstream(transcript, start),
Strand::Minus => Codon::upstream(transcript, start),
_ => Err(BuildCodonError::new("transcript with unknown Strand")),
}
}
pub fn downstream(transcript: &Transcript, start: &u32) -> Result<Self, BuildCodonError> {
Codon::sanity_check(transcript, start)?;
let mut len: u32 = 0;
let mut fragments: Vec<CodonFragment> = vec![];
for exon in transcript.exons() {
if !exon.is_coding() {
continue;
}
let cds_start = exon.cds_start().unwrap(); match intersect(
(
&cds_start,
&exon.cds_end().unwrap(), ),
(start, &(cmp::max(start, &cds_start) + (2 - len))),
) {
None => continue,
Some((start, end)) => {
fragments.push(CodonFragment::new(
transcript.chrom(),
start,
end,
Frame::from_int((3 - len) % 3).unwrap(), transcript.strand(),
));
len += end - start + 1
}
}
if len >= 3 {
break;
}
}
Codon::new(fragments)
}
pub fn upstream(transcript: &Transcript, start: &u32) -> Result<Self, BuildCodonError> {
Codon::sanity_check(transcript, start)?;
let mut len: u32 = 0;
let mut fragments: Vec<CodonFragment> = vec![];
for exon in transcript.exons().iter().rev() {
if !exon.is_coding() {
continue;
}
let cds_end = exon.cds_end().unwrap();
match intersect(
(&exon.cds_start().unwrap(), &cds_end),
(&(cmp::min(start, &cds_end) - (3 - len - 1)), start),
) {
None => continue,
Some((start, end)) => {
len += end - start + 1;
fragments.push(CodonFragment::new(
transcript.chrom(),
start,
end,
Frame::from_int(len % 3).unwrap(),
transcript.strand(),
));
}
}
if len >= 3 {
break;
}
}
fragments.reverse();
Codon::new(fragments)
}
pub fn start(&self) -> &u32 {
self.fragments[0].start()
}
pub fn end(&self) -> &u32 {
self.fragments.last().unwrap().end()
}
pub fn fragments(&self) -> &Vec<CodonFragment> {
&self.fragments
}
pub fn to_tuple(self) -> Vec<(u32, u32, Frame)> {
let mut res = vec![];
for frag in self.fragments() {
res.push((*frag.start(), *frag.end(), frag.frame_offset()));
}
res
}
fn sanity_check(transcript: &Transcript, start: &u32) -> Result<(), BuildCodonError> {
if !transcript.is_coding() {
return Err(BuildCodonError::new("transcript is non-coding"));
}
if start > &transcript.cds_end().unwrap() {
return Err(BuildCodonError::new("start is downstream of the CDS"));
}
if start < &transcript.cds_start().unwrap() {
return Err(BuildCodonError::new("start is upstream of the CDS"));
}
Ok(())
}
}
#[derive(Debug)]
pub struct CodonFragment {
chrom: String,
start: u32,
end: u32,
frame_offset: Frame,
strand: Strand,
}
impl CodonFragment {
pub fn new(chrom: &str, start: u32, end: u32, frame_offset: Frame, strand: Strand) -> Self {
CodonFragment {
chrom: chrom.to_string(),
start,
end,
frame_offset,
strand,
}
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> u32 {
self.end - self.start + 1
}
pub fn chrom(&self) -> &str {
&self.chrom
}
pub fn start(&self) -> &u32 {
&self.start
}
pub fn end(&self) -> &u32 {
&self.end
}
pub fn frame_offset(&self) -> Frame {
self.frame_offset
}
pub fn strand(&self) -> Strand {
self.strand
}
}