1use std::{ffi::CStr, ops::Deref};
2
3use edlib_sys::{edlibAlignmentToCigar, EdlibAlignConfig, EDLIB_STATUS_OK};
4use param::EdlibAlignParam;
5
6pub mod edlib_sys;
7
8pub mod param;
9pub mod utils;
10
11#[derive(Debug)]
12struct AlignResultGuard(edlib_sys::EdlibAlignResult);
13impl AlignResultGuard {
14 fn target_start_ends(&self) -> Vec<(usize, usize)> {
15 let mut start_ends = Vec::new();
16 if self.0.numLocations > 0 {
17 for i in 0..self.0.numLocations {
18 unsafe {
19 let start = if self.startLocations.is_null() {
20 0
21 } else {
22 *self.0.startLocations.add(i as usize)
23 };
24 let end = *self.0.endLocations.add(i as usize);
25 start_ends.push((start as usize, end as usize));
26 }
27 }
28 }
29
30 start_ends
31 }
32}
33
34impl Deref for AlignResultGuard {
35 type Target = edlib_sys::EdlibAlignResult;
36 fn deref(&self) -> &Self::Target {
37 &self.0
38 }
39}
40
41impl From<edlib_sys::EdlibAlignResult> for AlignResultGuard {
42 fn from(result: edlib_sys::EdlibAlignResult) -> Self {
43 AlignResultGuard(result)
44 }
45}
46
47struct AlignCigarGuard(*mut i8);
48
49impl Deref for AlignCigarGuard {
50 type Target = *mut i8;
51 fn deref(&self) -> &Self::Target {
52 &self.0
53 }
54}
55
56impl Drop for AlignCigarGuard {
57 fn drop(&mut self) {
58 unsafe {
59 libc::free(self.0 as *mut std::ffi::c_void);
60 }
61 }
62}
63
64impl From<*mut i8> for AlignCigarGuard {
65 fn from(cigar_str: *mut i8) -> Self {
66 AlignCigarGuard(cigar_str)
67 }
68}
69
70impl Drop for AlignResultGuard {
71 fn drop(&mut self) {
72 unsafe {
73 edlib_sys::edlibFreeAlignResult(self.0);
74 }
75 }
76}
77
78#[derive(Debug)]
79pub struct EdlibAlignResult {
80 pub edit_distance: i32,
81 pub alphabet_length: i32,
82 pub locations: Vec<(usize, usize)>,
83 pub cigar: Option<String>,
84}
85
86pub fn edlib_align(
87 query: &[u8],
88 target: &[u8],
89 aln_param: &EdlibAlignParam,
90) -> Result<EdlibAlignResult, String> {
91 let config = EdlibAlignConfig {
92 k: aln_param.k(),
93 mode: match aln_param.mode() {
94 param::AlignMode::Global => edlib_sys::EdlibAlignMode_EDLIB_MODE_NW,
95 param::AlignMode::Prefix => edlib_sys::EdlibAlignMode_EDLIB_MODE_SHW,
96 param::AlignMode::Infix => edlib_sys::EdlibAlignMode_EDLIB_MODE_HW,
97 },
98 task: match aln_param.task() {
99 param::AlignTask::Distance => edlib_sys::EdlibAlignTask_EDLIB_TASK_DISTANCE,
100 param::AlignTask::Locations => edlib_sys::EdlibAlignTask_EDLIB_TASK_LOC,
101 param::AlignTask::Path => edlib_sys::EdlibAlignTask_EDLIB_TASK_PATH,
102 },
103 additionalEqualities: if aln_param.additional_eq_pairs().len() > 0 {
104 aln_param.additional_eq_pairs().as_ptr()
105 } else {
106 std::ptr::null()
107 },
108 additionalEqualitiesLength: aln_param.additional_eq_pairs().len() as i32,
109 };
110
111 let edlib_raw_res: AlignResultGuard = unsafe {
112 edlib_sys::edlibAlign(
113 query.as_ptr() as *const i8,
114 query.len() as i32,
115 target.as_ptr() as *const i8,
116 target.len() as i32,
117 config,
118 )
119 .into()
120 };
121
122 let align_cigar_str = if aln_param.task() == param::AlignTask::Path
125 && aln_param.cigar_fmt() != param::CigarFmt::NoCigar
126 && edlib_raw_res.status == EDLIB_STATUS_OK as i32
127 {
128 let cigar_fmt = match aln_param.cigar_fmt() {
129 param::CigarFmt::Standard => edlib_sys::EdlibCigarFormat_EDLIB_CIGAR_STANDARD,
130 param::CigarFmt::Extended => edlib_sys::EdlibCigarFormat_EDLIB_CIGAR_EXTENDED,
131 _ => panic!("Invalid cigar format"),
132 };
133
134 unsafe {
135 let cigar_str_guard: AlignCigarGuard = edlibAlignmentToCigar(
136 edlib_raw_res.alignment,
137 edlib_raw_res.alignmentLength,
138 cigar_fmt,
139 )
140 .into();
141 Some(
142 CStr::from_ptr(*cigar_str_guard)
143 .to_str()
144 .unwrap()
145 .to_string(),
146 )
147 }
148 } else {
149 None
150 };
151
152 if edlib_raw_res.status != EDLIB_STATUS_OK as i32 {
153 Err(format!(
154 "Edlib alignment failed with status: {}",
155 edlib_raw_res.status
156 ))
157 } else {
158 Ok(EdlibAlignResult {
160 edit_distance: edlib_raw_res.editDistance,
161 alphabet_length: edlib_raw_res.alphabetLength,
162 locations: edlib_raw_res.target_start_ends(),
163 cigar: align_cigar_str,
164 })
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::utils::reverse_complement;
172
173 #[test]
174 fn test_edlib_align() {
175 let query = b"ATAATTTGTTCTCTGCGTTGACGTGCTTGAACCCTTAGCATCTTCTAAAATCTTTTTGACTTCATTCTTGTGTTTCCTTTAAGTTTTGTTATTTTCTTCTTCAAGTTCTGTGCAATTTAATAGCTTGTCTGCGTCATCGAATATCTCTGTTAATATCTCTTTCACGTTTATCCCATTACATCTTTCAGGCACCCCAATGCGAACTTTTTAAGTAACGCAAGAATAGCGATGAAATTAGGGACCC";
176
177 let query_rc = &reverse_complement(query);
178
179 let target = b"GGTCCTAACTTTCATCGAATTACTTGGCGTTACTTAAAAGTCGCATGGGTCCATTGAAAGATGTAATGGATAACGCTGAAAGAGATAATTAACAAGATATCGATGACGCAGAACAAGCTAAGTTAAATGGCACAGAAACCTTGAAGAAGAAATAAACCAAAAACTTAAGAAAGCCAAGAAAAGTTCAAAAAGATTTTTAAGAAGATGCTAAGGTTCAGCAGTCAACAGCAAGAAACAAATTTTAT";
180
181 let param = EdlibAlignParam::default();
182 let aln_res = edlib_align(query, target, ¶m);
183 println!("{:?}", aln_res);
184
185 let aln_res = edlib_align(query_rc, target, ¶m);
186 println!("{:?}", aln_res);
187 }
188
189 #[test]
190 fn test_edlib_align2() {
191 let query = b"elephant";
192 let target = b"telephone";
193
194 let mut param = EdlibAlignParam::default();
195 param.set_task(param::AlignTask::Path);
197 param.set_cigar_fmt(param::CigarFmt::Extended);
198 let aln_res = edlib_align(query, target, ¶m);
199 println!("{:?}", aln_res);
200 }
201}