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 #[allow(clippy::len_without_is_empty)]
68 #[deprecated(since = "0.23.0", note = "Use `Record::length` instead.")]
69 pub fn len(&self) -> u64 {
70 self.length()
71 }
72
73 pub fn length(&self) -> u64 {
83 self.length
84 }
85
86 pub fn offset(&self) -> u64 {
96 self.offset
97 }
98
99 pub fn line_bases(&self) -> u64 {
109 self.line_bases
110 }
111
112 pub fn line_width(&self) -> u64 {
122 self.line_width
123 }
124
125 pub fn query(&self, interval: Interval) -> io::Result<u64> {
140 let start = interval
141 .start()
142 .map(|position| usize::from(position) - 1)
143 .unwrap_or_default();
144
145 let start =
146 u64::try_from(start).map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
147
148 let pos = self.offset()
149 + start / self.line_bases() * self.line_width()
150 + start % self.line_bases();
151
152 Ok(pos)
153 }
154}
155
156#[derive(Clone, Debug, Eq, PartialEq)]
158pub enum ParseError {
159 Empty,
161 MissingField(Field),
163 InvalidField(Field, std::num::ParseIntError),
165}
166
167impl error::Error for ParseError {
168 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
169 match self {
170 Self::InvalidField(_, e) => Some(e),
171 _ => None,
172 }
173 }
174}
175
176impl fmt::Display for ParseError {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 match self {
179 Self::Empty => f.write_str("empty input"),
180 Self::MissingField(field) => write!(f, "missing field: {field:?}"),
181 Self::InvalidField(field, _) => write!(f, "invalid field: {field:?}"),
182 }
183 }
184}
185
186impl FromStr for Record {
187 type Err = ParseError;
188
189 fn from_str(s: &str) -> Result<Self, Self::Err> {
190 if s.is_empty() {
191 return Err(ParseError::Empty);
192 }
193
194 let mut fields = s.splitn(MAX_FIELDS, FIELD_DELIMITER);
195
196 let name = parse_string(&mut fields, Field::Name)?;
197 let len = parse_u64(&mut fields, Field::Length)?;
198 let offset = parse_u64(&mut fields, Field::Offset)?;
199 let line_bases = parse_u64(&mut fields, Field::LineBases)?;
200 let line_width = parse_u64(&mut fields, Field::LineWidth)?;
201
202 Ok(Self::new(name, len, offset, line_bases, line_width))
203 }
204}
205
206fn parse_string<'a, I>(fields: &mut I, field: Field) -> Result<String, ParseError>
207where
208 I: Iterator<Item = &'a str>,
209{
210 fields
211 .next()
212 .ok_or(ParseError::MissingField(field))
213 .map(|s| s.into())
214}
215
216fn parse_u64<'a, I>(fields: &mut I, field: Field) -> Result<u64, ParseError>
217where
218 I: Iterator<Item = &'a str>,
219{
220 fields
221 .next()
222 .ok_or(ParseError::MissingField(field))
223 .and_then(|s| s.parse().map_err(|e| ParseError::InvalidField(field, e)))
224}
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_from_str() {
232 assert_eq!(
233 "sq0\t10946\t4\t80\t81".parse(),
234 Ok(Record::new("sq0", 10946, 4, 80, 81))
235 );
236
237 assert_eq!("".parse::<Record>(), Err(ParseError::Empty));
238
239 assert_eq!(
240 "sq0".parse::<Record>(),
241 Err(ParseError::MissingField(Field::Length))
242 );
243
244 assert!(matches!(
245 "sq0\tnoodles".parse::<Record>(),
246 Err(ParseError::InvalidField(Field::Length, _))
247 ));
248 }
249}