1mod loader;
7mod schema;
8mod store;
9
10use crate::model::types::{BondOrder, Element, Point, StandardResidue};
11
12pub fn get_template(name: &str) -> Option<TemplateView<'_>> {
22 store::get_store()
23 .templates_by_name
24 .get(name)
25 .map(TemplateView::new)
26}
27
28#[derive(Debug, Clone, Copy)]
30pub struct TemplateView<'a> {
31 inner: &'a store::InternalTemplate,
32}
33
34impl<'a> TemplateView<'a> {
35 pub fn new(inner: &'a store::InternalTemplate) -> Self {
41 Self { inner }
42 }
43
44 pub fn name(&self) -> &'a str {
50 &self.inner.schema.info.name
51 }
52
53 pub fn standard_name(&self) -> StandardResidue {
59 self.inner.schema.info.standard_name
60 }
61
62 pub fn charge(&self) -> i32 {
68 self.inner.schema.info.charge
69 }
70
71 pub fn heavy_atoms(&self) -> impl Iterator<Item = (&'a str, Element, Point)> {
77 self.inner
78 .schema
79 .atoms
80 .iter()
81 .map(|a| (a.name.as_str(), a.element, Point::from(a.pos)))
82 }
83
84 pub fn hydrogens(
91 &self,
92 ) -> impl Iterator<Item = (&'a str, Point, impl Iterator<Item = &'a str>)> {
93 self.inner.schema.hydrogens.iter().map(|h| {
94 (
95 h.name.as_str(),
96 Point::from(h.pos),
97 h.anchors.iter().map(|s| s.as_str()),
98 )
99 })
100 }
101
102 pub fn bonds(&self) -> impl Iterator<Item = (&'a str, &'a str, BondOrder)> {
108 self.inner
109 .schema
110 .bonds
111 .iter()
112 .map(|b| (b.a1.as_str(), b.a2.as_str(), b.order))
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use crate::model::types::{BondOrder, Element};
120
121 fn create_mock_template(
122 name: &str,
123 standard_name: StandardResidue,
124 charge: i32,
125 atoms: Vec<schema::TemplateHeavyAtom>,
126 hydrogens: Vec<schema::TemplateHydrogen>,
127 bonds: Vec<schema::TemplateBond>,
128 ) -> store::InternalTemplate {
129 let schema = schema::ResidueTemplateFile {
130 info: schema::TemplateInfo {
131 name: name.to_string(),
132 standard_name,
133 charge,
134 },
135 atoms,
136 hydrogens,
137 bonds,
138 };
139 store::InternalTemplate { schema }
140 }
141
142 fn create_simple_mock_template() -> store::InternalTemplate {
143 let atoms = vec![
144 schema::TemplateHeavyAtom {
145 name: "CA".to_string(),
146 element: Element::C,
147 pos: [0.0, 0.0, 0.0],
148 },
149 schema::TemplateHeavyAtom {
150 name: "CB".to_string(),
151 element: Element::C,
152 pos: [1.0, 0.0, 0.0],
153 },
154 ];
155
156 let hydrogens = vec![schema::TemplateHydrogen {
157 name: "HA".to_string(),
158 pos: [0.5, 1.0, 0.0],
159 anchors: vec!["CA".to_string()],
160 }];
161
162 let bonds = vec![schema::TemplateBond {
163 a1: "CA".to_string(),
164 a2: "CB".to_string(),
165 order: BondOrder::Single,
166 }];
167
168 create_mock_template("TEST", StandardResidue::ALA, 0, atoms, hydrogens, bonds)
169 }
170
171 #[test]
172 fn get_template_returns_none_for_nonexistent_template() {
173 let result = get_template("NONEXISTENT");
174 assert!(result.is_none());
175 }
176
177 #[test]
178 fn get_template_returns_some_for_existing_template() {
179 let result = get_template("ALA");
180 assert!(result.is_some());
181 }
182
183 #[test]
184 fn template_view_name_returns_correct_name() {
185 let mock_template = create_simple_mock_template();
186 let view = TemplateView::new(&mock_template);
187
188 assert_eq!(view.name(), "TEST");
189 }
190
191 #[test]
192 fn template_view_standard_name_returns_correct_standard_name() {
193 let mock_template = create_simple_mock_template();
194 let view = TemplateView::new(&mock_template);
195
196 assert_eq!(view.standard_name(), StandardResidue::ALA);
197 }
198
199 #[test]
200 fn template_view_charge_returns_correct_charge() {
201 let mock_template = create_simple_mock_template();
202 let view = TemplateView::new(&mock_template);
203
204 assert_eq!(view.charge(), 0);
205 }
206
207 #[test]
208 fn template_view_charge_returns_correct_charge_for_charged_template() {
209 let atoms = vec![schema::TemplateHeavyAtom {
210 name: "N".to_string(),
211 element: Element::N,
212 pos: [0.0, 0.0, 0.0],
213 }];
214 let mock_template =
215 create_mock_template("LYS", StandardResidue::LYS, 1, atoms, vec![], vec![]);
216 let view = TemplateView::new(&mock_template);
217
218 assert_eq!(view.charge(), 1);
219 }
220
221 #[test]
222 fn template_view_heavy_atoms_returns_correct_iterator() {
223 let mock_template = create_simple_mock_template();
224 let view = TemplateView::new(&mock_template);
225
226 let heavy_atoms: Vec<_> = view.heavy_atoms().collect();
227
228 assert_eq!(heavy_atoms.len(), 2);
229
230 assert_eq!(heavy_atoms[0].0, "CA");
231 assert_eq!(heavy_atoms[0].1, Element::C);
232 assert_eq!(heavy_atoms[0].2, Point::new(0.0, 0.0, 0.0));
233
234 assert_eq!(heavy_atoms[1].0, "CB");
235 assert_eq!(heavy_atoms[1].1, Element::C);
236 assert_eq!(heavy_atoms[1].2, Point::new(1.0, 0.0, 0.0));
237 }
238
239 #[test]
240 fn template_view_heavy_atoms_returns_empty_iterator_for_template_without_atoms() {
241 let mock_template =
242 create_mock_template("EMPTY", StandardResidue::GLY, 0, vec![], vec![], vec![]);
243 let view = TemplateView::new(&mock_template);
244
245 let heavy_atoms: Vec<_> = view.heavy_atoms().collect();
246
247 assert_eq!(heavy_atoms.len(), 0);
248 }
249
250 #[test]
251 fn template_view_hydrogens_returns_correct_iterator() {
252 let mock_template = create_simple_mock_template();
253 let view = TemplateView::new(&mock_template);
254
255 let hydrogens: Vec<_> = view.hydrogens().collect();
256
257 assert_eq!(hydrogens.len(), 1);
258
259 let (name, pos, _) = &hydrogens[0];
260 assert_eq!(*name, "HA");
261 assert_eq!(*pos, Point::new(0.5, 1.0, 0.0));
262 }
263
264 #[test]
265 fn template_view_hydrogens_returns_empty_iterator_for_template_without_hydrogens() {
266 let atoms = vec![schema::TemplateHeavyAtom {
267 name: "CA".to_string(),
268 element: Element::C,
269 pos: [0.0, 0.0, 0.0],
270 }];
271 let mock_template =
272 create_mock_template("NO_H", StandardResidue::GLY, 0, atoms, vec![], vec![]);
273 let view = TemplateView::new(&mock_template);
274
275 let hydrogens: Vec<_> = view.hydrogens().collect();
276
277 assert_eq!(hydrogens.len(), 0);
278 }
279
280 #[test]
281 fn template_view_hydrogens_handles_multiple_anchors() {
282 let atoms = vec![
283 schema::TemplateHeavyAtom {
284 name: "CA".to_string(),
285 element: Element::C,
286 pos: [0.0, 0.0, 0.0],
287 },
288 schema::TemplateHeavyAtom {
289 name: "CB".to_string(),
290 element: Element::C,
291 pos: [1.0, 0.0, 0.0],
292 },
293 ];
294
295 let hydrogens = vec![schema::TemplateHydrogen {
296 name: "HA".to_string(),
297 pos: [0.5, 1.0, 0.0],
298 anchors: vec!["CA".to_string(), "CB".to_string()],
299 }];
300
301 let mock_template = create_mock_template(
302 "MULTI_ANCHOR",
303 StandardResidue::ALA,
304 0,
305 atoms,
306 hydrogens,
307 vec![],
308 );
309 let view = TemplateView::new(&mock_template);
310
311 let hydrogens_vec: Vec<_> = view.hydrogens().collect();
312 assert_eq!(hydrogens_vec.len(), 1);
313
314 let (name, pos, _) = &hydrogens_vec[0];
315 assert_eq!(*name, "HA");
316 assert_eq!(*pos, Point::new(0.5, 1.0, 0.0));
317 }
318
319 #[test]
320 fn template_view_bonds_returns_correct_iterator() {
321 let mock_template = create_simple_mock_template();
322 let view = TemplateView::new(&mock_template);
323
324 let bonds: Vec<_> = view.bonds().collect();
325
326 assert_eq!(bonds.len(), 1);
327
328 let (a1, a2, order) = bonds[0];
329 assert_eq!(a1, "CA");
330 assert_eq!(a2, "CB");
331 assert_eq!(order, BondOrder::Single);
332 }
333
334 #[test]
335 fn template_view_bonds_returns_empty_iterator_for_template_without_bonds() {
336 let atoms = vec![schema::TemplateHeavyAtom {
337 name: "CA".to_string(),
338 element: Element::C,
339 pos: [0.0, 0.0, 0.0],
340 }];
341 let mock_template =
342 create_mock_template("NO_BONDS", StandardResidue::GLY, 0, atoms, vec![], vec![]);
343 let view = TemplateView::new(&mock_template);
344
345 let bonds: Vec<_> = view.bonds().collect();
346
347 assert_eq!(bonds.len(), 0);
348 }
349
350 #[test]
351 fn template_view_bonds_handles_different_bond_orders() {
352 let atoms = vec![
353 schema::TemplateHeavyAtom {
354 name: "C1".to_string(),
355 element: Element::C,
356 pos: [0.0, 0.0, 0.0],
357 },
358 schema::TemplateHeavyAtom {
359 name: "C2".to_string(),
360 element: Element::C,
361 pos: [1.0, 0.0, 0.0],
362 },
363 schema::TemplateHeavyAtom {
364 name: "N1".to_string(),
365 element: Element::N,
366 pos: [2.0, 0.0, 0.0],
367 },
368 ];
369
370 let bonds = vec![
371 schema::TemplateBond {
372 a1: "C1".to_string(),
373 a2: "C2".to_string(),
374 order: BondOrder::Single,
375 },
376 schema::TemplateBond {
377 a1: "C2".to_string(),
378 a2: "N1".to_string(),
379 order: BondOrder::Double,
380 },
381 schema::TemplateBond {
382 a1: "C1".to_string(),
383 a2: "N1".to_string(),
384 order: BondOrder::Triple,
385 },
386 ];
387
388 let mock_template =
389 create_mock_template("BONDS", StandardResidue::ALA, 0, atoms, vec![], bonds);
390 let view = TemplateView::new(&mock_template);
391
392 let bonds_vec: Vec<_> = view.bonds().collect();
393
394 assert_eq!(bonds_vec.len(), 3);
395 assert_eq!(bonds_vec[0].2, BondOrder::Single);
396 assert_eq!(bonds_vec[1].2, BondOrder::Double);
397 assert_eq!(bonds_vec[2].2, BondOrder::Triple);
398 }
399
400 #[test]
401 fn template_view_bonds_handles_aromatic_bonds() {
402 let atoms = vec![
403 schema::TemplateHeavyAtom {
404 name: "C1".to_string(),
405 element: Element::C,
406 pos: [0.0, 0.0, 0.0],
407 },
408 schema::TemplateHeavyAtom {
409 name: "C2".to_string(),
410 element: Element::C,
411 pos: [1.0, 0.0, 0.0],
412 },
413 ];
414
415 let bonds = vec![schema::TemplateBond {
416 a1: "C1".to_string(),
417 a2: "C2".to_string(),
418 order: BondOrder::Aromatic,
419 }];
420
421 let mock_template =
422 create_mock_template("AROMATIC", StandardResidue::PHE, 0, atoms, vec![], bonds);
423 let view = TemplateView::new(&mock_template);
424
425 let bonds_vec: Vec<_> = view.bonds().collect();
426
427 assert_eq!(bonds_vec.len(), 1);
428 assert_eq!(bonds_vec[0].2, BondOrder::Aromatic);
429 }
430
431 #[test]
432 fn template_view_clone_creates_identical_copy() {
433 let mock_template = create_simple_mock_template();
434 let view1 = TemplateView::new(&mock_template);
435 let view2 = view1;
436
437 assert_eq!(view1.name(), view2.name());
438 assert_eq!(view1.standard_name(), view2.standard_name());
439 assert_eq!(view1.charge(), view2.charge());
440
441 let atoms1: Vec<_> = view1.heavy_atoms().collect();
442 let atoms2: Vec<_> = view2.heavy_atoms().collect();
443 assert_eq!(atoms1, atoms2);
444
445 let bonds1: Vec<_> = view1.bonds().collect();
446 let bonds2: Vec<_> = view2.bonds().collect();
447 assert_eq!(bonds1, bonds2);
448 }
449
450 #[test]
451 fn template_view_debug_formatting() {
452 let mock_template = create_simple_mock_template();
453 let view = TemplateView::new(&mock_template);
454
455 let debug_str = format!("{:?}", view);
456 assert!(debug_str.contains("TemplateView"));
457 }
458
459 #[test]
460 fn get_template_with_real_database_templates() {
461 let templates_to_test = vec![
462 "ALA", "GLY", "VAL", "LEU", "ILE", "MET", "PHE", "PRO", "SER", "THR", "TYR", "CYS",
463 "ASN", "GLN", "ASP", "GLU", "LYS", "ARG", "HID", "HIE", "HIP", "HOH",
464 ];
465
466 for template_name in templates_to_test {
467 let result = get_template(template_name);
468 assert!(
469 result.is_some(),
470 "Template '{}' should exist",
471 template_name
472 );
473
474 let template = result.unwrap();
475 assert_eq!(template.name(), template_name);
476 }
477 }
478
479 #[test]
480 fn template_view_with_real_template_has_expected_properties() {
481 if let Some(ala_template) = get_template("ALA") {
482 assert_eq!(ala_template.name(), "ALA");
483 assert_eq!(ala_template.standard_name(), StandardResidue::ALA);
484 assert_eq!(ala_template.charge(), 0);
485
486 let heavy_atoms: Vec<_> = ala_template.heavy_atoms().collect();
487 assert!(!heavy_atoms.is_empty());
488
489 let bonds: Vec<_> = ala_template.bonds().collect();
490 assert!(!bonds.is_empty());
491 }
492 }
493
494 #[test]
495 fn template_view_with_real_template_water_properties() {
496 if let Some(hoh_template) = get_template("HOH") {
497 assert_eq!(hoh_template.name(), "HOH");
498 assert_eq!(hoh_template.standard_name(), StandardResidue::HOH);
499 assert_eq!(hoh_template.charge(), 0);
500
501 let heavy_atoms: Vec<_> = hoh_template.heavy_atoms().collect();
502 assert_eq!(heavy_atoms.len(), 1);
503 assert_eq!(heavy_atoms[0].1, Element::O);
504
505 let hydrogens: Vec<_> = hoh_template.hydrogens().collect();
506 assert!(!hydrogens.is_empty());
507 }
508 }
509}