noodles_fasta/fai/
record.rs1mod field;
2
3use std::{error, fmt, io, str::FromStr};
4
5use bstr::{BStr, BString};
6use noodles_core::region::Interval;
7
8use self::field::Field;
9
10const FIELD_DELIMITER: char = '\t';
11const MAX_FIELDS: usize = 5;
12
13#[derive(Clone, Debug, Default, Eq, PartialEq)]
15pub struct Record {
16 name: BString,
17 length: u64,
18 offset: u64,
19 line_bases: u64,
20 line_width: u64,
21}
22
23impl Record {
24 pub fn new<N>(name: N, length: u64, offset: u64, line_bases: u64, line_width: u64) -> Self
33 where
34 N: Into<BString>,
35 {
36 Self {
37 name: name.into(),
38 length,
39 offset,
40 line_bases,
41 line_width,
42 }
43 }
44
45 pub fn name(&self) -> &BStr {
55 self.name.as_ref()
56 }
57
58 pub fn length(&self) -> u64 {
68 self.length
69 }
70
71 pub fn offset(&self) -> u64 {
81 self.offset
82 }
83
84 pub fn line_bases(&self) -> u64 {
94 self.line_bases
95 }
96
97 pub fn line_width(&self) -> u64 {
107 self.line_width
108 }
109
110 pub fn query(&self, interval: Interval) -> io::Result<u64> {
125 let start = interval
126 .start()
127 .map(|position| usize::from(position) - 1)
128 .unwrap_or_default();
129
130 let start =
131 u64::try_from(start).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
132
133 let pos = self.offset()
134 + start / self.line_bases() * self.line_width()
135 + start % self.line_bases();
136
137 Ok(pos)
138 }
139}
140
141#[derive(Clone, Debug, Eq, PartialEq)]
143pub enum ParseError {
144 Empty,
146 MissingField(Field),
148 InvalidField(Field, std::num::ParseIntError),
150}
151
152impl error::Error for ParseError {
153 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
154 match self {
155 Self::InvalidField(_, e) => Some(e),
156 _ => None,
157 }
158 }
159}
160
161impl fmt::Display for ParseError {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 match self {
164 Self::Empty => f.write_str("empty input"),
165 Self::MissingField(field) => write!(f, "missing field: {field:?}"),
166 Self::InvalidField(field, _) => write!(f, "invalid field: {field:?}"),
167 }
168 }
169}
170
171impl FromStr for Record {
172 type Err = ParseError;
173
174 fn from_str(s: &str) -> Result<Self, Self::Err> {
175 if s.is_empty() {
176 return Err(ParseError::Empty);
177 }
178
179 let mut fields = s.splitn(MAX_FIELDS, FIELD_DELIMITER);
180
181 let name = parse_string(&mut fields, Field::Name)?;
182 let len = parse_u64(&mut fields, Field::Length)?;
183 let offset = parse_u64(&mut fields, Field::Offset)?;
184 let line_bases = parse_u64(&mut fields, Field::LineBases)?;
185 let line_width = parse_u64(&mut fields, Field::LineWidth)?;
186
187 Ok(Self::new(name, len, offset, line_bases, line_width))
188 }
189}
190
191fn parse_string<'a, I>(fields: &mut I, field: Field) -> Result<String, ParseError>
192where
193 I: Iterator<Item = &'a str>,
194{
195 fields
196 .next()
197 .ok_or(ParseError::MissingField(field))
198 .map(|s| s.into())
199}
200
201fn parse_u64<'a, I>(fields: &mut I, field: Field) -> Result<u64, ParseError>
202where
203 I: Iterator<Item = &'a str>,
204{
205 fields
206 .next()
207 .ok_or(ParseError::MissingField(field))
208 .and_then(|s| s.parse().map_err(|e| ParseError::InvalidField(field, e)))
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_from_str() {
217 assert_eq!(
218 "sq0\t10946\t4\t80\t81".parse(),
219 Ok(Record::new("sq0", 10946, 4, 80, 81))
220 );
221
222 assert_eq!("".parse::<Record>(), Err(ParseError::Empty));
223
224 assert_eq!(
225 "sq0".parse::<Record>(),
226 Err(ParseError::MissingField(Field::Length))
227 );
228
229 assert!(matches!(
230 "sq0\tnoodles".parse::<Record>(),
231 Err(ParseError::InvalidField(Field::Length, _))
232 ));
233 }
234}