read_structure/
lib.rs

1//! Read structures is a library for working with strings that describe how the bases in a sequencing run
2//! should be allocated into logical reads.
3//!
4//! Each read structure is made up of one or more read segments which are in turn a segment type.
5//!
6//! For more details see [here](https://github.com/fulcrumgenomics/fgbio/wiki/Read-Structures)
7//!
8//! # Example
9//!
10//! Parsing a complex read structure.
11//!
12//! ```rust
13//! use std::str::FromStr;
14//! use read_structure::ReadStructure;
15//!
16//! let rs = ReadStructure::from_str("76T8B8B76T").unwrap();
17//! let templates: String = rs.templates().map(|s| s.to_string()).collect();
18//! assert_eq!(templates, "76T76T");
19//! ```
20//!
21//! Extracting segments from an actual read based on the read structure:
22//!
23//! ```rust
24//! use std::convert::TryFrom;
25//! use std::str::FromStr;
26//! use read_structure::{
27//!     ReadStructure,
28//!     SegmentType,
29//! };
30//! let read_sequence = b"\
31//!     AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGGGGGGGGCCCCCCCCTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT"
32//! .to_vec();
33//! let kind_of_interest = SegmentType::Template;
34//! let rs = ReadStructure::from_str("76T8B8B76T").unwrap();
35//!
36//! let mut sections = vec![];
37//! for segment in rs.segments_by_type(kind_of_interest) {
38//!     sections.push(segment.extract_bases(read_sequence.as_slice()).unwrap())
39//! }
40//! assert_eq!(sections, vec![
41//!     b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
42//!     b"TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT"
43//! ]);
44//! ```
45
46#![allow(unused, clippy::must_use_candidate)]
47#![allow(dead_code)]
48
49mod read_segment;
50mod read_structure;
51mod segment_type;
52
53pub use crate::read_structure::*;
54pub use read_segment::*;
55pub use segment_type::*;
56use thiserror::Error;
57
58#[derive(Debug, Error)]
59pub enum ReadStructureError {
60    #[error("Example")]
61    Example,
62
63    #[error("Invalid read structure: {0}")]
64    InvalidReadStructure(String),
65
66    #[error("Mismatching bases and quals lengths: {bases_len}, {quals_len}")]
67    MismatchingBasesAndQualsLen { bases_len: usize, quals_len: usize },
68
69    #[error("Read structure missing length information: {}[{}]{}", .0.prefix, .0.error, .0.suffix)]
70    ReadStructureMissingLengthInformation(ErrorMessageParts),
71
72    #[error("Read structure missing operator: {}[{}]{}", .0.prefix, .0.error, .0.suffix)]
73    ReadStructureMissingOperator(ErrorMessageParts),
74
75    #[error("Read structure had unknown type: {}[{}]{}", .0.prefix, .0.error, .0.suffix)]
76    ReadStructureHadUnknownType(ErrorMessageParts),
77
78    #[error("Read structure contains zero elements")]
79    ReadStructureContainsZeroElements,
80
81    #[error("Read structure contains a non-terminal segment that has an indefinite length: {0}")]
82    ReadStructureNonTerminalIndefiniteLengthReadSegment(ReadSegment),
83
84    #[error("Read ends before start of segment: {0}")]
85    ReadEndsBeforeSegment(ReadSegment),
86
87    #[error("Read ends before end of segment: {0}")]
88    ReadEndsAfterSegment(ReadSegment),
89
90    #[error("ReadSegment too short: {0}")]
91    ReadSegmentTooShort(String),
92
93    #[error("ReadSegment str contained more than one segment: {0}")]
94    ReadSegmentMultipleSegments(String),
95
96    #[error("ReadSegment must have length > 0 or `+`: {}[{}]{}", .0.prefix, .0.error, .0.suffix)]
97    ReadSegmentLengthZero(ErrorMessageParts),
98
99    #[error("Invalid SegementType: {0}")]
100    ReadSegmentTypeInvalid(char),
101}
102
103/// Helper struct for isolating the erroneous portion of a string.
104#[derive(Debug)]
105pub struct ErrorMessageParts {
106    prefix: String,
107    error: String,
108    suffix: String,
109}
110
111impl ErrorMessageParts {
112    fn new(chars: &[char], start: usize, end: usize) -> Self {
113        let prefix: String = chars.iter().take(start).collect();
114        let error: String = chars.iter().skip(start).take(end - start).collect();
115        let suffix: String = if end == chars.len() {
116            "".to_string()
117        } else {
118            chars.iter().skip(end).take(chars.len() - end).collect()
119        };
120        Self { prefix, error, suffix }
121    }
122}