use std::path::Path;
use crate::consequence::ConsequenceResult;
use crate::error::VarEffectError;
use crate::fasta::FastaReader;
use crate::hgvs_reverse::GenomicVariant;
use crate::locate::LocateIndex;
use crate::transcript::TranscriptStore;
use crate::types::TranscriptModel;
pub struct VarEffect {
transcripts: TranscriptStore,
fasta: FastaReader,
}
impl VarEffect {
pub fn new(transcripts: TranscriptStore, fasta: FastaReader) -> Self {
Self { transcripts, fasta }
}
pub fn open(transcript_models_path: &Path, genome_path: &Path) -> Result<Self, VarEffectError> {
let transcripts = TranscriptStore::load_from_path(transcript_models_path)?;
let fasta = FastaReader::open(genome_path)?;
Ok(Self::new(transcripts, fasta))
}
pub fn open_with_patch_aliases(
transcript_models_path: &Path,
genome_path: &Path,
patch_aliases_csv: Option<&Path>,
) -> Result<Self, VarEffectError> {
let transcripts = TranscriptStore::load_from_path(transcript_models_path)?;
let fasta = FastaReader::open_with_patch_aliases(genome_path, patch_aliases_csv)?;
Ok(Self::new(transcripts, fasta))
}
pub fn annotate(
&self,
chrom: &str,
pos: u64,
ref_allele: &[u8],
alt_allele: &[u8],
) -> Result<Vec<ConsequenceResult>, VarEffectError> {
crate::consequence::annotate(
chrom,
pos,
ref_allele,
alt_allele,
&self.transcripts,
&self.fasta,
)
}
pub fn annotate_to_vep_json(
&self,
chrom: &str,
pos: u64,
ref_allele: &[u8],
alt_allele: &[u8],
assembly: &str,
) -> Result<serde_json::Value, VarEffectError> {
let results = self.annotate(chrom, pos, ref_allele, alt_allele)?;
Ok(crate::vep_json::to_vep_json_array(
chrom, pos, ref_allele, alt_allele, assembly, &results,
))
}
pub fn resolve_hgvs_c(&self, hgvs: &str) -> Result<GenomicVariant, VarEffectError> {
crate::hgvs_reverse::resolve_hgvs_c(hgvs, &self.transcripts, &self.fasta)
}
pub fn fetch_base(&self, chrom: &str, pos: u64) -> Result<u8, VarEffectError> {
self.fasta.fetch_base(chrom, pos)
}
pub fn fetch_sequence(
&self,
chrom: &str,
start: u64,
end: u64,
) -> Result<Vec<u8>, VarEffectError> {
self.fasta.fetch_sequence(chrom, start, end)
}
pub fn verify_ref(
&self,
chrom: &str,
pos: u64,
ref_allele: &[u8],
) -> Result<bool, VarEffectError> {
self.fasta.verify_ref(chrom, pos, ref_allele)
}
pub fn chrom_length(&self, chrom: &str) -> Option<u64> {
self.fasta.chrom_length(chrom)
}
pub fn anchor_prepend_indel(
&self,
chrom: &str,
pos_1based: u64,
ref_allele: &str,
alt_allele: &str,
) -> Result<Option<(u64, String, String)>, VarEffectError> {
let is_deletion = alt_allele == "-";
let is_insertion = ref_allele == "-";
if !is_deletion && !is_insertion {
return Ok(None);
}
let anchor_pos_0 = match pos_1based.checked_sub(2) {
Some(p) => p,
None => {
let chrom_len =
self.chrom_length(chrom)
.ok_or_else(|| VarEffectError::ChromNotFound {
chrom: chrom.to_string(),
})?;
return Err(VarEffectError::CoordinateOutOfRange {
chrom: chrom.to_string(),
start: 0,
end: 0,
chrom_len,
});
}
};
let anchor_byte = self.fetch_base(chrom, anchor_pos_0)?;
let anchor = anchor_byte as char;
let new_pos = pos_1based - 1;
let (new_ref, new_alt) = if is_deletion {
(format!("{anchor}{ref_allele}"), anchor.to_string())
} else {
(anchor.to_string(), format!("{anchor}{alt_allele}"))
};
Ok(Some((new_pos, new_ref, new_alt)))
}
pub fn left_align_indel(
&self,
chrom: &str,
pos_1based: u64,
ref_allele: &str,
alt_allele: &str,
) -> Result<Option<(u64, String, String)>, VarEffectError> {
let orig_pos = pos_1based;
let orig_ref = ref_allele;
let orig_alt = alt_allele;
let mut pos = pos_1based;
let mut r: Vec<u8> = ref_allele.as_bytes().to_vec();
let mut a: Vec<u8> = alt_allele.as_bytes().to_vec();
loop {
if pos <= 1 {
break;
}
let (Some(&r_last), Some(&a_last)) = (r.last(), a.last()) else {
break;
};
if r_last != a_last {
break;
}
r.pop();
a.pop();
pos -= 1;
if r.is_empty() {
r.push(self.fasta.fetch_base(chrom, pos - 1)?);
}
if a.is_empty() {
a.push(self.fasta.fetch_base(chrom, pos - 1)?);
}
}
while r.len() > 1 && a.len() > 1 && r[0] == a[0] {
r.remove(0);
a.remove(0);
pos += 1;
}
let new_ref = String::from_utf8(r).map_err(|_| VarEffectError::InvalidAllele)?;
let new_alt = String::from_utf8(a).map_err(|_| VarEffectError::InvalidAllele)?;
if pos == orig_pos && new_ref == orig_ref && new_alt == orig_alt {
Ok(None)
} else {
Ok(Some((pos, new_ref, new_alt)))
}
}
pub fn get_by_accession(&self, accession: &str) -> Option<(&TranscriptModel, &LocateIndex)> {
self.transcripts.get_by_accession(accession)
}
pub fn query_overlap(
&self,
chrom: &str,
start: u64,
end: u64,
) -> Vec<(&TranscriptModel, &LocateIndex)> {
self.transcripts.query_overlap(chrom, start, end)
}
pub fn transcripts(&self) -> &TranscriptStore {
&self.transcripts
}
pub fn fasta(&self) -> &FastaReader {
&self.fasta
}
}