1use std::fmt;
4use std::io;
5
6pub type Result<T> = std::result::Result<T, Error>;
8
9#[derive(Debug)]
14#[non_exhaustive]
15pub enum Error {
16 Io(io::Error),
18 Parse {
20 line: u64,
22 kind: ParseError,
24 },
25 UnknownFormat {
27 hint: String,
29 },
30 InvalidByte {
32 id: String,
34 pos: usize,
36 byte: u8,
38 },
39 LengthMismatch {
41 id: String,
43 seq: usize,
45 quality: usize,
47 },
48 MissingQuality {
50 id: String,
52 },
53 Index(String),
55 UnknownSequence(String),
57 PairMismatch {
59 pairs_read: u64,
61 first: String,
63 second: String,
65 },
66 PairTruncated {
68 pairs_read: u64,
70 missing: &'static str,
72 },
73 OutOfBounds {
75 id: String,
77 start: u64,
79 end: u64,
81 length: u64,
83 },
84 TooLarge {
89 line: u64,
91 what: &'static str,
93 limit: usize,
95 },
96 FeatureDisabled(&'static str),
98 Unsupported(&'static str),
100 Other(String),
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
106#[non_exhaustive]
107pub enum ParseError {
108 ExpectedHeader {
110 found: u8,
112 },
113 ExpectedSeparator {
115 found: u8,
117 },
118 UnexpectedEof {
120 expected: &'static str,
122 },
123 EmptyId,
125}
126
127impl fmt::Display for ParseError {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 match self {
130 ParseError::ExpectedHeader { found } => {
131 write!(
132 f,
133 "expected a record header ('>' or '@'), found {}",
134 Byte(*found)
135 )
136 }
137 ParseError::ExpectedSeparator { found } => {
138 write!(
139 f,
140 "expected the FASTQ '+' separator line, found {}",
141 Byte(*found)
142 )
143 }
144 ParseError::UnexpectedEof { expected } => {
145 write!(f, "unexpected end of input, expected {expected}")
146 }
147 ParseError::EmptyId => write!(f, "record header does not contain an identifier"),
148 }
149 }
150}
151
152impl fmt::Display for Error {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 match self {
155 Error::Io(e) => write!(f, "I/O error: {e}"),
156 Error::Parse { line, kind } => write!(f, "parse error on line {line}: {kind}"),
157 Error::UnknownFormat { hint } => {
158 write!(f, "cannot determine sequence format ({hint})")
159 }
160 Error::InvalidByte { id, pos, byte } => write!(
161 f,
162 "record {}: invalid residue {} at position {pos}",
163 Id(id),
164 Byte(*byte)
165 ),
166 Error::LengthMismatch { id, seq, quality } => write!(
167 f,
168 "record {}: sequence has {seq} bases but quality has {quality} scores",
169 Id(id)
170 ),
171 Error::MissingQuality { id } => {
172 write!(f, "record {}: FASTQ output requires quality scores", Id(id))
173 }
174 Error::Index(msg) => write!(f, "invalid FASTA index: {msg}"),
175 Error::UnknownSequence(name) => write!(f, "sequence {name:?} is not in the index"),
176 Error::PairMismatch {
177 pairs_read,
178 first,
179 second,
180 } => write!(
181 f,
182 "reads are mispaired after {pairs_read} pairs: {first:?} and {second:?} are not \
183 mates. Are the two files sorted the same way?"
184 ),
185 Error::PairTruncated {
186 pairs_read,
187 missing,
188 } => write!(
189 f,
190 "{missing} ended after {pairs_read} pairs, the other did not"
191 ),
192 Error::OutOfBounds {
193 id,
194 start,
195 end,
196 length,
197 } => write!(
198 f,
199 "region {start}..{end} is out of bounds for record {} of length {length}",
200 Id(id)
201 ),
202 Error::TooLarge { line, what, limit } => write!(
203 f,
204 "line {line}: {what} exceeds the configured limit of {limit} bytes"
205 ),
206 Error::FeatureDisabled(feature) => {
207 write!(
208 f,
209 "this build of fastx was compiled without the {feature:?} feature"
210 )
211 }
212 Error::Unsupported(what) => write!(f, "unsupported: {what}"),
213 Error::Other(message) => f.write_str(message),
214 }
215 }
216}
217
218impl std::error::Error for Error {
219 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
220 match self {
221 Error::Io(e) => Some(e),
222 _ => None,
223 }
224 }
225}
226
227impl From<io::Error> for Error {
228 fn from(e: io::Error) -> Self {
229 Error::Io(e)
230 }
231}
232
233impl From<Error> for io::Error {
234 fn from(e: Error) -> Self {
235 match e {
236 Error::Io(e) => e,
237 other => io::Error::new(io::ErrorKind::InvalidData, other),
238 }
239 }
240}
241
242impl Error {
243 pub(crate) fn parse(line: u64, kind: ParseError) -> Self {
244 Error::Parse { line, kind }
245 }
246
247 pub fn is_malformed(&self) -> bool {
251 !matches!(self, Error::Io(_))
252 }
253}
254
255struct Byte(u8);
257
258impl fmt::Display for Byte {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 match self.0 {
261 b'\n' => f.write_str("'\\n'"),
262 b'\r' => f.write_str("'\\r'"),
263 b'\t' => f.write_str("'\\t'"),
264 b if b.is_ascii_graphic() => write!(f, "'{}'", b as char),
265 b => write!(f, "0x{b:02x}"),
266 }
267 }
268}
269
270struct Id<'a>(&'a str);
272
273impl fmt::Display for Id<'_> {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 if self.0.is_empty() {
276 f.write_str("<unnamed>")
277 } else {
278 write!(f, "{:?}", self.0)
279 }
280 }
281}