Skip to content

Commit 7258d5b

Browse files
committed
add legacy spec loader compatibility
1 parent c1e2151 commit 7258d5b

File tree

6 files changed

+638
-44
lines changed

6 files changed

+638
-44
lines changed

src/compat.rs

Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
use serde::Deserialize;
2+
use std::path::Path;
3+
4+
use crate::models::assay::{Assay, LibKit, LibProtocol, SeqKit, SeqProtocol};
5+
use crate::models::file::File;
6+
use crate::models::onlist::Onlist;
7+
use crate::models::read::Read;
8+
use crate::models::region::Region;
9+
10+
#[derive(Clone, Debug, Deserialize)]
11+
pub struct AssayCompat {
12+
pub seqspec_version: Option<String>,
13+
pub assay_id: Option<String>,
14+
pub name: Option<String>,
15+
pub doi: Option<String>,
16+
pub date: Option<String>,
17+
pub description: Option<String>,
18+
pub modalities: Option<Vec<String>>,
19+
pub lib_struct: Option<String>,
20+
21+
pub sequence_protocol: Option<CompatField<CompatSeqProtocol>>,
22+
pub sequence_kit: Option<CompatField<CompatSeqKit>>,
23+
pub library_protocol: Option<CompatField<CompatLibProtocol>>,
24+
pub library_kit: Option<CompatField<CompatLibKit>>,
25+
26+
pub sequence_spec: Option<Vec<CompatRead>>,
27+
pub library_spec: Option<Vec<CompatRegion>>,
28+
}
29+
30+
impl AssayCompat {
31+
pub fn into_assay(self) -> Assay {
32+
let modalities = self.modalities.unwrap_or_default();
33+
Assay::new(
34+
self.assay_id.unwrap_or_default(),
35+
self.name.unwrap_or_default(),
36+
self.doi.unwrap_or_default(),
37+
self.date.unwrap_or_default(),
38+
self.description.unwrap_or_default(),
39+
modalities.clone(),
40+
self.lib_struct.unwrap_or_default(),
41+
self.sequence_spec
42+
.unwrap_or_default()
43+
.into_iter()
44+
.map(CompatRead::into_read)
45+
.collect(),
46+
self.library_spec
47+
.unwrap_or_default()
48+
.into_iter()
49+
.map(CompatRegion::into_region)
50+
.collect(),
51+
normalize_field(
52+
self.sequence_protocol,
53+
&modalities,
54+
|value, modality| SeqProtocol {
55+
protocol_id: value.clone(),
56+
name: value,
57+
modality,
58+
},
59+
CompatSeqProtocol::into_seqprotocol,
60+
),
61+
normalize_field(
62+
self.sequence_kit,
63+
&modalities,
64+
|value, modality| SeqKit {
65+
kit_id: value.clone(),
66+
name: Some(value),
67+
modality,
68+
},
69+
CompatSeqKit::into_seqkit,
70+
),
71+
normalize_field(
72+
self.library_protocol,
73+
&modalities,
74+
|value, modality| LibProtocol {
75+
protocol_id: value.clone(),
76+
name: value,
77+
modality,
78+
},
79+
CompatLibProtocol::into_libprotocol,
80+
),
81+
normalize_field(
82+
self.library_kit,
83+
&modalities,
84+
|value, modality| LibKit {
85+
kit_id: value.clone(),
86+
name: Some(value),
87+
modality,
88+
},
89+
CompatLibKit::into_libkit,
90+
),
91+
self.seqspec_version,
92+
)
93+
}
94+
}
95+
96+
#[derive(Clone, Debug, Deserialize)]
97+
#[serde(untagged)]
98+
pub enum CompatField<T> {
99+
Text(String),
100+
Item(T),
101+
Items(Vec<CompatFieldItem<T>>),
102+
}
103+
104+
#[derive(Clone, Debug, Deserialize)]
105+
#[serde(untagged)]
106+
pub enum CompatFieldItem<T> {
107+
Text(String),
108+
Item(T),
109+
}
110+
111+
fn normalize_field<Input, Output, FText, FInto>(
112+
value: Option<CompatField<Input>>,
113+
modalities: &[String],
114+
from_text: FText,
115+
into_output: FInto,
116+
) -> Option<Vec<Output>>
117+
where
118+
FText: Fn(String, String) -> Output,
119+
FInto: Fn(Input) -> Output,
120+
{
121+
let value = value?;
122+
let mut out = Vec::new();
123+
124+
match value {
125+
CompatField::Text(text) => {
126+
for modality in modalities {
127+
out.push(from_text(text.clone(), modality.clone()));
128+
}
129+
}
130+
CompatField::Item(item) => out.push(into_output(item)),
131+
CompatField::Items(items) => {
132+
for item in items {
133+
match item {
134+
CompatFieldItem::Text(text) => {
135+
for modality in modalities {
136+
out.push(from_text(text.clone(), modality.clone()));
137+
}
138+
}
139+
CompatFieldItem::Item(item) => out.push(into_output(item)),
140+
}
141+
}
142+
}
143+
}
144+
145+
Some(out)
146+
}
147+
148+
#[derive(Clone, Debug, Deserialize)]
149+
pub struct CompatSeqProtocol {
150+
pub protocol_id: Option<String>,
151+
pub name: Option<String>,
152+
pub modality: Option<String>,
153+
}
154+
155+
impl CompatSeqProtocol {
156+
fn into_seqprotocol(self) -> SeqProtocol {
157+
SeqProtocol {
158+
protocol_id: self.protocol_id.unwrap_or_else(|| "auto-id".to_string()),
159+
name: self.name.unwrap_or_default(),
160+
modality: self.modality.unwrap_or_default(),
161+
}
162+
}
163+
}
164+
165+
#[derive(Clone, Debug, Deserialize)]
166+
pub struct CompatSeqKit {
167+
pub kit_id: Option<String>,
168+
pub name: Option<String>,
169+
pub modality: Option<String>,
170+
}
171+
172+
impl CompatSeqKit {
173+
fn into_seqkit(self) -> SeqKit {
174+
SeqKit {
175+
kit_id: self.kit_id.unwrap_or_default(),
176+
name: self.name,
177+
modality: self.modality.unwrap_or_default(),
178+
}
179+
}
180+
}
181+
182+
#[derive(Clone, Debug, Deserialize)]
183+
pub struct CompatLibProtocol {
184+
pub protocol_id: Option<String>,
185+
pub name: Option<String>,
186+
pub modality: Option<String>,
187+
}
188+
189+
impl CompatLibProtocol {
190+
fn into_libprotocol(self) -> LibProtocol {
191+
LibProtocol {
192+
protocol_id: self.protocol_id.unwrap_or_default(),
193+
name: self.name.unwrap_or_default(),
194+
modality: self.modality.unwrap_or_default(),
195+
}
196+
}
197+
}
198+
199+
#[derive(Clone, Debug, Deserialize)]
200+
pub struct CompatLibKit {
201+
pub kit_id: Option<String>,
202+
pub name: Option<String>,
203+
pub modality: Option<String>,
204+
}
205+
206+
impl CompatLibKit {
207+
fn into_libkit(self) -> LibKit {
208+
LibKit {
209+
kit_id: self.kit_id.unwrap_or_default(),
210+
name: self.name,
211+
modality: self.modality.unwrap_or_default(),
212+
}
213+
}
214+
}
215+
216+
#[derive(Clone, Debug, Deserialize)]
217+
pub struct CompatFile {
218+
pub file_id: Option<String>,
219+
pub filename: Option<String>,
220+
pub filetype: Option<String>,
221+
pub filesize: Option<i64>,
222+
pub url: Option<String>,
223+
pub urltype: Option<String>,
224+
pub md5: Option<String>,
225+
}
226+
227+
impl CompatFile {
228+
fn into_file(self) -> File {
229+
let filename = self.filename.unwrap_or_default();
230+
let basename = if filename.is_empty() {
231+
String::new()
232+
} else {
233+
Path::new(&filename)
234+
.file_name()
235+
.map(|name| name.to_string_lossy().into_owned())
236+
.unwrap_or_default()
237+
};
238+
let filetype = if let Some(filetype) = self.filetype {
239+
filetype
240+
} else if filename.is_empty() {
241+
String::new()
242+
} else {
243+
Path::new(&filename)
244+
.extension()
245+
.map(|ext| ext.to_string_lossy().into_owned())
246+
.unwrap_or_default()
247+
};
248+
249+
File::new(
250+
self.file_id.unwrap_or(basename),
251+
filename,
252+
filetype,
253+
self.filesize.unwrap_or_default(),
254+
self.url.unwrap_or_default(),
255+
self.urltype.unwrap_or_else(|| "local".to_string()),
256+
self.md5.unwrap_or_default(),
257+
)
258+
}
259+
}
260+
261+
#[derive(Clone, Debug, Deserialize)]
262+
pub struct CompatOnlist {
263+
pub file_id: Option<String>,
264+
pub filename: Option<String>,
265+
pub filetype: Option<String>,
266+
pub filesize: Option<i64>,
267+
pub url: Option<String>,
268+
pub urltype: Option<String>,
269+
pub md5: Option<String>,
270+
}
271+
272+
impl CompatOnlist {
273+
fn into_onlist(self) -> Onlist {
274+
Onlist::new(
275+
self.file_id.unwrap_or_default(),
276+
self.filename.unwrap_or_default(),
277+
self.filetype.unwrap_or_default(),
278+
self.filesize.unwrap_or_default(),
279+
self.url.unwrap_or_default(),
280+
self.urltype.unwrap_or_else(|| "local".to_string()),
281+
self.md5.unwrap_or_default(),
282+
)
283+
}
284+
}
285+
286+
#[derive(Clone, Debug, Deserialize)]
287+
pub struct CompatRead {
288+
pub read_id: Option<String>,
289+
pub name: Option<String>,
290+
pub modality: Option<String>,
291+
pub primer_id: Option<String>,
292+
pub min_len: Option<i64>,
293+
pub max_len: Option<i64>,
294+
pub strand: Option<String>,
295+
pub files: Option<Vec<CompatFile>>,
296+
}
297+
298+
impl CompatRead {
299+
fn into_read(self) -> Read {
300+
let read_id = self.read_id.unwrap_or_default();
301+
Read::new(
302+
read_id.clone(),
303+
self.name.unwrap_or_else(|| read_id.clone()),
304+
self.modality.unwrap_or_default(),
305+
self.primer_id.unwrap_or_default(),
306+
self.min_len.unwrap_or_default(),
307+
self.max_len.unwrap_or_default(),
308+
self.strand.unwrap_or_else(|| "pos".to_string()),
309+
self.files
310+
.unwrap_or_default()
311+
.into_iter()
312+
.map(CompatFile::into_file)
313+
.collect(),
314+
)
315+
}
316+
}
317+
318+
#[derive(Clone, Debug, Deserialize)]
319+
pub struct CompatRegion {
320+
pub region_id: Option<String>,
321+
pub region_type: Option<String>,
322+
pub name: Option<String>,
323+
pub sequence_type: Option<String>,
324+
pub sequence: Option<String>,
325+
pub min_len: Option<i64>,
326+
pub max_len: Option<i64>,
327+
pub onlist: Option<CompatOnlist>,
328+
pub regions: Option<Vec<CompatRegion>>,
329+
}
330+
331+
impl CompatRegion {
332+
fn into_region(self) -> Region {
333+
let region_id = self.region_id.unwrap_or_default();
334+
let min_len = self.min_len.unwrap_or_default();
335+
let max_len = self
336+
.max_len
337+
.unwrap_or(if min_len == 0 { 1024 } else { min_len });
338+
let sequence_type = self.sequence_type.unwrap_or_else(|| "fixed".to_string());
339+
let sequence = match self.sequence {
340+
Some(sequence) => sequence,
341+
None if sequence_type == "random" => "X".repeat(min_len as usize),
342+
None if sequence_type == "onlist" => "N".repeat(min_len as usize),
343+
None => String::new(),
344+
};
345+
346+
Region::new(
347+
region_id.clone(),
348+
self.region_type.unwrap_or_else(|| region_id.clone()),
349+
self.name.unwrap_or(region_id),
350+
sequence_type,
351+
sequence,
352+
min_len,
353+
max_len,
354+
self.onlist.map(CompatOnlist::into_onlist),
355+
self.regions
356+
.unwrap_or_default()
357+
.into_iter()
358+
.map(CompatRegion::into_region)
359+
.collect(),
360+
)
361+
}
362+
}

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
mod compat;
2+
13
pub mod models;
24

35
pub use models::{file, region, read, onlist, assay, coordinate};
@@ -18,4 +20,4 @@ pub mod seqspec_check;
1820
pub mod seqspec_onlist;
1921

2022
// #[cfg(feature = "python-binding")]
21-
// mod py_module; // lives in src/py_module.rs
23+
// mod py_module; // lives in src/py_module.rs

0 commit comments

Comments
 (0)