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