-
Notifications
You must be signed in to change notification settings - Fork 26
/
Copy patherrors.rs
163 lines (148 loc) · 4.92 KB
/
errors.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! The errors needletail can return; only when parsing FASTA/FASTQ files
use crate::parser::Format;
use std::error::Error as StdError;
use std::fmt;
use std::io;
/// Represents where we were in a file when an error occurred.
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ErrorPosition {
/// Line number where the error occurred (starting with 1)
pub line: u64,
/// ID of record if available
pub id: Option<String>,
}
impl fmt::Display for ErrorPosition {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(id) = self.id.as_ref() {
write!(f, "record '{id}' at ")?;
}
write!(f, "line {}", self.line)
}
}
/// The type of error that occurred during file parsing
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ParseErrorKind {
/// An error happened during file/stream input/output
Io,
/// The file didn't start with `@` or `>` and we didn't know what to expect yet
UnknownFormat,
/// Invalid start byte of record encountered (expected `@` in FASTQ and `>` in FASTA)
InvalidStart,
/// The separator line in a FASTQ file is not valid (no `+`)
InvalidSeparator,
/// Sequence and quality lengths are not equal (in a FASTQ file only)
UnequalLengths,
/// Truncated record found
UnexpectedEnd,
/// The file appears to be empty
EmptyFile,
}
/// The only error type that needletail returns
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ParseError {
/// A description of what went wrong
pub msg: String,
/// The type of error that occurred
pub kind: ParseErrorKind,
/// Position within file
pub position: ErrorPosition,
/// The format of the file we were parsing
pub format: Option<Format>,
}
impl ParseError {
pub fn new_invalid_start(byte_found: u8, position: ErrorPosition, format: Format) -> Self {
let msg = format!(
"Expected '{}' but found '{}",
format.start_char(),
(byte_found as char).escape_default()
);
Self {
kind: ParseErrorKind::InvalidStart,
msg,
position,
format: Some(format),
}
}
pub fn new_invalid_separator(byte_found: u8, position: ErrorPosition) -> Self {
let msg = format!(
"Expected '+' separator but found '{}",
(byte_found as char).escape_default()
);
Self {
kind: ParseErrorKind::InvalidSeparator,
msg,
position,
format: Some(Format::Fastq),
}
}
pub fn new_unknown_format(byte_found: u8) -> Self {
let msg = format!(
"Expected '@' or '>' at the start of the file but found '{}'.",
(byte_found as char).escape_default()
);
Self {
kind: ParseErrorKind::UnknownFormat,
msg,
position: ErrorPosition::default(),
format: Some(Format::Fastq),
}
}
pub fn new_unequal_length(seq_len: usize, qual_len: usize, position: ErrorPosition) -> Self {
let msg = format!("Sequence length is {seq_len} but quality length is {qual_len}");
Self {
kind: ParseErrorKind::UnequalLengths,
msg,
position,
format: Some(Format::Fastq),
}
}
pub fn new_unexpected_end(position: ErrorPosition, format: Format) -> Self {
Self {
msg: String::new(),
kind: ParseErrorKind::UnexpectedEnd,
position,
format: Some(format),
}
}
pub fn new_empty_file() -> Self {
Self {
msg: String::from("Failed to read the first two bytes. Is the file empty?"),
kind: ParseErrorKind::EmptyFile,
position: ErrorPosition::default(),
format: None,
}
}
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.kind {
ParseErrorKind::Io => write!(f, "I/O error: {}", self.msg),
ParseErrorKind::UnequalLengths
| ParseErrorKind::InvalidStart
| ParseErrorKind::UnknownFormat
| ParseErrorKind::EmptyFile
| ParseErrorKind::InvalidSeparator => write!(f, "{} ({})", self.msg, self.position),
ParseErrorKind::UnexpectedEnd => {
write!(f, "Unexpected end of input ({}).", self.position)
}
}
}
}
impl From<io::Error> for ParseError {
fn from(err: io::Error) -> Self {
Self {
msg: err.to_string(),
kind: ParseErrorKind::Io,
position: ErrorPosition::default(),
format: None,
}
}
}
impl StdError for ParseError {
fn cause(&self) -> Option<&dyn StdError> {
// Ideally we would pass the io::Error but we don't for simplicity sake
// since we wouldn't be able to `==` on the error kind otherwise.
// TODO: impl partialeq manually?
None
}
}