1use super::types::BondOrder;
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct Template {
17 pub name: String,
19 atom_names: Vec<String>,
21 bonds: Vec<(String, String, BondOrder)>,
23}
24
25impl Template {
26 pub fn new<S: Into<String>>(
62 name: S,
63 atom_names: Vec<String>,
64 bonds: Vec<(String, String, BondOrder)>,
65 ) -> Self {
66 debug_assert!(
67 bonds
68 .iter()
69 .all(|(a1, a2, _)| { atom_names.contains(a1) && atom_names.contains(a2) }),
70 "Bond in template '{}' refers to an atom name that does not exist in the atom list.",
71 name.into()
72 );
73
74 Self {
75 name: name.into(),
76 atom_names,
77 bonds,
78 }
79 }
80
81 pub fn has_bond(&self, name1: &str, name2: &str) -> bool {
109 self.bonds
110 .iter()
111 .any(|(a1, a2, _)| (a1 == name1 && a2 == name2) || (a1 == name2 && a2 == name1))
112 }
113
114 pub fn has_atom(&self, name: &str) -> bool {
136 self.atom_names.contains(&name.to_string())
137 }
138
139 pub fn atom_names(&self) -> &[String] {
156 &self.atom_names
157 }
158
159 pub fn bonds(&self) -> &[(String, String, BondOrder)] {
177 &self.bonds
178 }
179
180 pub fn atom_count(&self) -> usize {
195 self.atom_names.len()
196 }
197
198 pub fn bond_count(&self) -> usize {
216 self.bonds.len()
217 }
218}
219
220impl fmt::Display for Template {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 write!(
223 f,
224 "Template {{ name: \"{}\", atoms: {}, bonds: {} }}",
225 self.name,
226 self.atom_count(),
227 self.bond_count()
228 )
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn template_new_creates_correct_template() {
238 let atom_names = vec!["C1".to_string(), "C2".to_string(), "O1".to_string()];
239 let bonds = vec![
240 ("C1".to_string(), "C2".to_string(), BondOrder::Single),
241 ("C2".to_string(), "O1".to_string(), BondOrder::Double),
242 ];
243
244 let template = Template::new("LIG", atom_names.clone(), bonds.clone());
245
246 assert_eq!(template.name, "LIG");
247 assert_eq!(template.atom_names, atom_names);
248 assert_eq!(template.bonds, bonds);
249 }
250
251 #[test]
252 fn template_new_with_empty_atom_names() {
253 let atom_names = Vec::new();
254 let bonds = Vec::new();
255
256 let template = Template::new("EMPTY", atom_names, bonds);
257
258 assert_eq!(template.name, "EMPTY");
259 assert_eq!(template.atom_count(), 0);
260 assert_eq!(template.bond_count(), 0);
261 }
262
263 #[test]
264 fn template_new_with_empty_bonds() {
265 let atom_names = vec!["C1".to_string(), "C2".to_string()];
266 let bonds = Vec::new();
267
268 let template = Template::new("NO_BONDS", atom_names.clone(), bonds);
269
270 assert_eq!(template.name, "NO_BONDS");
271 assert_eq!(template.atom_names, atom_names);
272 assert_eq!(template.bonds, Vec::new());
273 }
274
275 #[test]
276 fn template_new_with_string_name() {
277 let atom_names = vec!["N1".to_string()];
278 let bonds = Vec::new();
279
280 let template = Template::new(String::from("ATP"), atom_names.clone(), bonds);
281
282 assert_eq!(template.name, "ATP");
283 assert_eq!(template.atom_names, atom_names);
284 }
285
286 #[test]
287 fn template_has_bond_returns_true_for_existing_bond() {
288 let atom_names = vec!["C1".to_string(), "C2".to_string(), "O1".to_string()];
289 let bonds = vec![
290 ("C1".to_string(), "C2".to_string(), BondOrder::Single),
291 ("C2".to_string(), "O1".to_string(), BondOrder::Double),
292 ];
293 let template = Template::new("LIG", atom_names, bonds);
294
295 assert!(template.has_bond("C1", "C2"));
296 assert!(template.has_bond("C2", "O1"));
297 }
298
299 #[test]
300 fn template_has_bond_returns_true_for_reverse_order() {
301 let atom_names = vec!["C1".to_string(), "C2".to_string()];
302 let bonds = vec![("C1".to_string(), "C2".to_string(), BondOrder::Single)];
303 let template = Template::new("LIG", atom_names, bonds);
304
305 assert!(template.has_bond("C2", "C1"));
306 }
307
308 #[test]
309 fn template_has_bond_returns_false_for_nonexistent_bond() {
310 let atom_names = vec!["C1".to_string(), "C2".to_string(), "O1".to_string()];
311 let bonds = vec![("C1".to_string(), "C2".to_string(), BondOrder::Single)];
312 let template = Template::new("LIG", atom_names, bonds);
313
314 assert!(!template.has_bond("C1", "O1"));
315 assert!(!template.has_bond("O1", "C2"));
316 }
317
318 #[test]
319 fn template_has_bond_returns_false_for_empty_template() {
320 let template = Template::new("EMPTY", Vec::new(), Vec::new());
321
322 assert!(!template.has_bond("C1", "C2"));
323 }
324
325 #[test]
326 fn template_has_atom_returns_true_for_existing_atom() {
327 let atom_names = vec!["C1".to_string(), "C2".to_string(), "O1".to_string()];
328 let bonds = Vec::new();
329 let template = Template::new("LIG", atom_names, bonds);
330
331 assert!(template.has_atom("C1"));
332 assert!(template.has_atom("C2"));
333 assert!(template.has_atom("O1"));
334 }
335
336 #[test]
337 fn template_has_atom_returns_false_for_nonexistent_atom() {
338 let atom_names = vec!["C1".to_string(), "C2".to_string()];
339 let bonds = Vec::new();
340 let template = Template::new("LIG", atom_names, bonds);
341
342 assert!(!template.has_atom("O1"));
343 assert!(!template.has_atom("N1"));
344 }
345
346 #[test]
347 fn template_has_atom_returns_false_for_empty_template() {
348 let template = Template::new("EMPTY", Vec::new(), Vec::new());
349
350 assert!(!template.has_atom("C1"));
351 }
352
353 #[test]
354 fn template_atom_names_returns_correct_slice() {
355 let atom_names = vec!["C1".to_string(), "C2".to_string(), "O1".to_string()];
356 let bonds = Vec::new();
357 let template = Template::new("LIG", atom_names.clone(), bonds);
358
359 let names = template.atom_names();
360
361 assert_eq!(names, atom_names.as_slice());
362 assert_eq!(names.len(), 3);
363 assert_eq!(names[0], "C1");
364 assert_eq!(names[1], "C2");
365 assert_eq!(names[2], "O1");
366 }
367
368 #[test]
369 fn template_atom_names_returns_empty_slice_for_empty_template() {
370 let template = Template::new("EMPTY", Vec::new(), Vec::new());
371
372 let names = template.atom_names();
373
374 assert_eq!(names, &[] as &[String]);
375 assert_eq!(names.len(), 0);
376 }
377
378 #[test]
379 fn template_bonds_returns_correct_slice() {
380 let atom_names = vec!["C1".to_string(), "C2".to_string(), "O1".to_string()];
381 let bonds = vec![
382 ("C1".to_string(), "C2".to_string(), BondOrder::Single),
383 ("C2".to_string(), "O1".to_string(), BondOrder::Double),
384 ];
385 let template = Template::new("LIG", atom_names, bonds.clone());
386
387 let template_bonds = template.bonds();
388
389 assert_eq!(template_bonds, bonds.as_slice());
390 assert_eq!(template_bonds.len(), 2);
391 assert_eq!(
392 template_bonds[0],
393 ("C1".to_string(), "C2".to_string(), BondOrder::Single)
394 );
395 assert_eq!(
396 template_bonds[1],
397 ("C2".to_string(), "O1".to_string(), BondOrder::Double)
398 );
399 }
400
401 #[test]
402 fn template_bonds_returns_empty_slice_for_empty_template() {
403 let template = Template::new("EMPTY", Vec::new(), Vec::new());
404
405 let bonds = template.bonds();
406
407 assert_eq!(bonds, &[]);
408 assert_eq!(bonds.len(), 0);
409 }
410
411 #[test]
412 fn template_atom_count_returns_correct_count() {
413 let atom_names = vec![
414 "C1".to_string(),
415 "C2".to_string(),
416 "O1".to_string(),
417 "N1".to_string(),
418 ];
419 let bonds = Vec::new();
420 let template = Template::new("LIG", atom_names, bonds);
421
422 assert_eq!(template.atom_count(), 4);
423 }
424
425 #[test]
426 fn template_atom_count_returns_zero_for_empty_template() {
427 let template = Template::new("EMPTY", Vec::new(), Vec::new());
428
429 assert_eq!(template.atom_count(), 0);
430 }
431
432 #[test]
433 fn template_bond_count_returns_correct_count() {
434 let atom_names = vec!["C1".to_string(), "C2".to_string(), "O1".to_string()];
435 let bonds = vec![
436 ("C1".to_string(), "C2".to_string(), BondOrder::Single),
437 ("C2".to_string(), "O1".to_string(), BondOrder::Double),
438 ("C1".to_string(), "O1".to_string(), BondOrder::Single),
439 ];
440 let template = Template::new("LIG", atom_names, bonds);
441
442 assert_eq!(template.bond_count(), 3);
443 }
444
445 #[test]
446 fn template_bond_count_returns_zero_for_empty_template() {
447 let template = Template::new("EMPTY", Vec::new(), Vec::new());
448
449 assert_eq!(template.bond_count(), 0);
450 }
451
452 #[test]
453 fn template_display_formats_correctly() {
454 let atom_names = vec!["C1".to_string(), "C2".to_string(), "O1".to_string()];
455 let bonds = vec![
456 ("C1".to_string(), "C2".to_string(), BondOrder::Single),
457 ("C2".to_string(), "O1".to_string(), BondOrder::Double),
458 ];
459 let template = Template::new("LIG", atom_names, bonds);
460
461 let display = format!("{}", template);
462 let expected = "Template { name: \"LIG\", atoms: 3, bonds: 2 }";
463
464 assert_eq!(display, expected);
465 }
466
467 #[test]
468 fn template_display_formats_empty_template_correctly() {
469 let template = Template::new("EMPTY", Vec::new(), Vec::new());
470
471 let display = format!("{}", template);
472 let expected = "Template { name: \"EMPTY\", atoms: 0, bonds: 0 }";
473
474 assert_eq!(display, expected);
475 }
476
477 #[test]
478 fn template_display_formats_template_with_special_characters() {
479 let atom_names = vec!["C1".to_string()];
480 let bonds = Vec::new();
481 let template = Template::new("LIG-123", atom_names, bonds);
482
483 let display = format!("{}", template);
484 let expected = "Template { name: \"LIG-123\", atoms: 1, bonds: 0 }";
485
486 assert_eq!(display, expected);
487 }
488
489 #[test]
490 fn template_clone_creates_identical_copy() {
491 let atom_names = vec!["C1".to_string(), "C2".to_string()];
492 let bonds = vec![("C1".to_string(), "C2".to_string(), BondOrder::Single)];
493 let template = Template::new("LIG", atom_names.clone(), bonds.clone());
494
495 let cloned = template.clone();
496
497 assert_eq!(template, cloned);
498 assert_eq!(template.name, cloned.name);
499 assert_eq!(template.atom_names, cloned.atom_names);
500 assert_eq!(template.bonds, cloned.bonds);
501 }
502
503 #[test]
504 fn template_partial_eq_compares_correctly() {
505 let atom_names = vec!["C1".to_string(), "C2".to_string()];
506 let bonds = vec![("C1".to_string(), "C2".to_string(), BondOrder::Single)];
507
508 let template1 = Template::new("LIG", atom_names.clone(), bonds.clone());
509 let template2 = Template::new("LIG", atom_names.clone(), bonds.clone());
510 let template3 = Template::new("DIF", atom_names.clone(), bonds.clone());
511
512 assert_eq!(template1, template2);
513 assert_ne!(template1, template3);
514 }
515
516 #[test]
517 fn template_with_complex_molecule() {
518 let atom_names = vec![
519 "C1".to_string(),
520 "C2".to_string(),
521 "O1".to_string(),
522 "H11".to_string(),
523 "H12".to_string(),
524 "H13".to_string(),
525 "H21".to_string(),
526 "H22".to_string(),
527 "H1".to_string(),
528 ];
529 let bonds = vec![
530 ("C1".to_string(), "C2".to_string(), BondOrder::Single),
531 ("C2".to_string(), "O1".to_string(), BondOrder::Single),
532 ("C1".to_string(), "H11".to_string(), BondOrder::Single),
533 ("C1".to_string(), "H12".to_string(), BondOrder::Single),
534 ("C1".to_string(), "H13".to_string(), BondOrder::Single),
535 ("C2".to_string(), "H21".to_string(), BondOrder::Single),
536 ("C2".to_string(), "H22".to_string(), BondOrder::Single),
537 ("O1".to_string(), "H1".to_string(), BondOrder::Single),
538 ];
539
540 let template = Template::new("ETOH", atom_names, bonds);
541
542 assert_eq!(template.name, "ETOH");
543 assert_eq!(template.atom_count(), 9);
544 assert_eq!(template.bond_count(), 8);
545
546 assert!(template.has_bond("C1", "C2"));
547 assert!(template.has_bond("C2", "O1"));
548 assert!(template.has_bond("O1", "H1"));
549
550 assert!(template.has_atom("C1"));
551 assert!(template.has_atom("H1"));
552 assert!(!template.has_atom("C3"));
553 }
554
555 #[test]
556 fn template_with_aromatic_bonds() {
557 let atom_names = vec!["C1".to_string(), "C2".to_string(), "C3".to_string()];
558 let bonds = vec![
559 ("C1".to_string(), "C2".to_string(), BondOrder::Aromatic),
560 ("C2".to_string(), "C3".to_string(), BondOrder::Aromatic),
561 ("C3".to_string(), "C1".to_string(), BondOrder::Aromatic),
562 ];
563
564 let template = Template::new("BENZENE_RING", atom_names, bonds);
565
566 assert_eq!(template.bond_count(), 3);
567 assert!(template.has_bond("C1", "C2"));
568 assert!(template.has_bond("C2", "C3"));
569 assert!(template.has_bond("C3", "C1"));
570 }
571
572 #[test]
573 fn template_with_triple_bond() {
574 let atom_names = vec!["N1".to_string(), "N2".to_string()];
575 let bonds = vec![("N1".to_string(), "N2".to_string(), BondOrder::Triple)];
576
577 let template = Template::new("N2", atom_names, bonds);
578
579 assert_eq!(template.bond_count(), 1);
580 assert!(template.has_bond("N1", "N2"));
581 assert!(!template.has_bond("N1", "N3"));
582 }
583}