csv_async/error.rs
1use std::error::Error as StdError;
2use std::fmt;
3use std::io;
4use std::result;
5
6use crate::byte_record::{ByteRecord, Position};
7#[cfg(feature = "with_serde")]
8use crate::deserializer::DeserializeError;
9
10/// A type alias for `Result<T, csv_async::Error>`.
11pub type Result<T> = result::Result<T, Error>;
12
13/// An error that can occur when processing CSV data.
14///
15/// This error can happen when writing or reading CSV data.
16///
17/// There are some important scenarios where an error is impossible to occur.
18/// For example, if a CSV reader is used on an in-memory buffer with the
19/// `flexible` option enabled and one is reading records as raw byte strings,
20/// then no error can occur.
21#[derive(Debug)]
22pub struct Error(Box<ErrorKind>);
23
24impl Error {
25 /// A crate private constructor for `Error`.
26 pub(crate) fn new(kind: ErrorKind) -> Error {
27 Error(Box::new(kind))
28 }
29
30 /// Return the specific type of this error.
31 pub fn kind(&self) -> &ErrorKind {
32 &self.0
33 }
34
35 /// Unwrap this error into its underlying type.
36 pub fn into_kind(self) -> ErrorKind {
37 *self.0
38 }
39
40 /// Returns true if this is an I/O error.
41 ///
42 /// If this is true, the underlying `ErrorKind` is guaranteed to be
43 /// `ErrorKind::Io`.
44 pub fn is_io_error(&self) -> bool {
45 matches!(*self.0, ErrorKind::Io(_))
46 }
47
48 /// Return the position for this error, if one exists.
49 ///
50 /// This is a convenience function that permits callers to easily access
51 /// the position on an error without doing case analysis on `ErrorKind`.
52 pub fn position(&self) -> Option<&Position> {
53 self.0.position()
54 }
55}
56
57/// The specific type of an error.
58#[derive(Debug)]
59#[non_exhaustive]
60pub enum ErrorKind {
61 /// An I/O error that occurred while reading CSV data.
62 Io(io::Error),
63 /// A UTF-8 decoding error that occured while reading CSV data into Rust
64 /// `String`s.
65 Utf8 {
66 /// The position of the record in which this error occurred, if
67 /// available.
68 pos: Option<Position>,
69 /// The corresponding UTF-8 error.
70 err: Utf8Error,
71 },
72 /// This error occurs when two records with an unequal number of fields
73 /// are found. This error only occurs when the `flexible` option in a
74 /// CSV reader/writer is disabled.
75 UnequalLengths {
76 /// The position of the first record with an unequal number of fields
77 /// to the previous record, if available.
78 pos: Option<Position>,
79 /// The expected number of fields in a record. This is the number of
80 /// fields in the record read prior to the record indicated by
81 /// `pos`.
82 expected_len: u64,
83 /// The number of fields in the bad record.
84 len: u64,
85 },
86 /// This error occurs when either the `byte_headers` or `headers` methods
87 /// are called on a CSV reader that was asked to `seek` before it parsed
88 /// the first record.
89 Seek,
90 /// An error of this kind occurs only when using the Serde serializer.
91 #[cfg(feature = "with_serde")]
92 Serialize(String),
93 /// An error of this kind occurs only when performing automatic
94 /// deserialization with serde.
95 #[cfg(feature = "with_serde")]
96 Deserialize {
97 /// The position of this error, if available.
98 pos: Option<Position>,
99 /// The deserialization error.
100 err: DeserializeError,
101 }
102}
103
104impl ErrorKind {
105 /// Return the position for this error, if one exists.
106 ///
107 /// This is a convenience function that permits callers to easily access
108 /// the position on an error without doing case analysis on `ErrorKind`.
109 pub fn position(&self) -> Option<&Position> {
110 match *self {
111 ErrorKind::Utf8 { ref pos, .. } => pos.as_ref(),
112 ErrorKind::UnequalLengths { ref pos, .. } => pos.as_ref(),
113 #[cfg(feature = "with_serde")]
114 ErrorKind::Deserialize{ ref pos, .. } => pos.as_ref(),
115 _ => None,
116 }
117 }
118}
119
120impl From<io::Error> for Error {
121 fn from(err: io::Error) -> Error {
122 Error::new(ErrorKind::Io(err))
123 }
124}
125
126impl From<Error> for io::Error {
127 fn from(err: Error) -> io::Error {
128 io::Error::new(io::ErrorKind::Other, err)
129 }
130}
131
132impl StdError for Error {}
133
134impl fmt::Display for Error {
135 #[allow(unreachable_patterns)]
136 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137 match *self.0 {
138 ErrorKind::Io(ref err) => err.fmt(f),
139 ErrorKind::Utf8 { pos: None, ref err } => {
140 write!(f, "CSV parse error: field {}: {err}", err.field() + 1)
141 }
142 ErrorKind::Utf8 { pos: Some(ref pos), ref err } => write!(
143 f,
144 "CSV parse error: record {} \
145 (line {}, field: {}, byte: {}): {err}",
146 pos.record(),
147 pos.line(),
148 err.field() + 1,
149 pos.byte(),
150 ),
151 ErrorKind::UnequalLengths { pos: None, expected_len, len } => {
152 write!(
153 f,
154 "CSV error: \
155 found record with {len} fields, but the previous record \
156 has {expected_len} fields"
157 )
158 }
159 ErrorKind::UnequalLengths {
160 pos: Some(ref pos),
161 expected_len,
162 len,
163 } => write!(
164 f,
165 "CSV error: record {} (line: {}, byte: {}): \
166 found record with {len} fields, but the previous record \
167 has {expected_len} fields",
168 pos.record(),
169 pos.line(),
170 pos.byte(),
171 ),
172 ErrorKind::Seek => write!(
173 f,
174 "CSV error: cannot access headers of CSV data \
175 when the parser was seeked before the first record \
176 could be read"
177 ),
178 #[cfg(feature = "with_serde")]
179 ErrorKind::Serialize(ref msg) => {
180 write!(f, "CSV serialize error: {msg}")
181 }
182 #[cfg(feature = "with_serde")]
183 ErrorKind::Deserialize { pos: None, ref err } => {
184 write!(f, "CSV deserialize error: {err}")
185 }
186 #[cfg(feature = "with_serde")]
187 ErrorKind::Deserialize {
188 pos: Some(ref pos),
189 ref err,
190 } => write!(
191 f,
192 "CSV deserialize error: record {} \
193 (line {}, byte: {}): {err}",
194 pos.record(),
195 pos.line(),
196 pos.byte(),
197 ),
198 _ => write!(f,"CSV other error")
199 }
200 }
201}
202
203/// A UTF-8 validation error during record conversion.
204///
205/// This occurs when attempting to convert a `ByteRecord` into a
206/// `StringRecord`.
207#[derive(Clone, Debug, Eq, PartialEq)]
208pub struct FromUtf8Error {
209 record: ByteRecord,
210 err: Utf8Error,
211}
212
213impl FromUtf8Error {
214 /// Create a new FromUtf8Error.
215 pub(crate) fn new(record: ByteRecord, err: Utf8Error) -> FromUtf8Error {
216 FromUtf8Error { record, err }
217 }
218
219 /// Access the underlying `ByteRecord` that failed UTF-8 validation.
220 pub fn into_byte_record(self) -> ByteRecord {
221 self.record
222 }
223
224 /// Access the underlying UTF-8 validation error.
225 pub fn utf8_error(&self) -> &Utf8Error {
226 &self.err
227 }
228}
229
230impl fmt::Display for FromUtf8Error {
231 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
232 self.err.fmt(f)
233 }
234}
235
236impl StdError for FromUtf8Error {
237 fn source(&self) -> Option<&(dyn StdError + 'static)> {
238 Some(&self.err)
239 }
240}
241
242/// A UTF-8 validation error.
243///
244/// This occurs when attempting to convert a `ByteRecord` into a
245/// `StringRecord`.
246///
247/// The error includes the index of the field that failed validation, and the
248/// last byte at which valid UTF-8 was verified.
249#[derive(Clone, Debug, Eq, PartialEq)]
250pub struct Utf8Error {
251 /// The field index of a byte record in which UTF-8 validation failed.
252 field: usize,
253 /// The index into the given field up to which valid UTF-8 was verified.
254 valid_up_to: usize,
255}
256
257/// Create a new UTF-8 error.
258pub fn new_utf8_error(field: usize, valid_up_to: usize) -> Utf8Error {
259 Utf8Error { field, valid_up_to }
260}
261
262impl Utf8Error {
263 /// The field index (zero based) of a byte record in which UTF-8 validation failed.
264 pub fn field(&self) -> usize {
265 self.field
266 }
267 /// The index (zero based) into the given field up to which valid UTF-8 was verified.
268 pub fn valid_up_to(&self) -> usize {
269 self.valid_up_to
270 }
271}
272
273impl StdError for Utf8Error {}
274
275impl fmt::Display for Utf8Error {
276 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
277 write!(
278 f,
279 "invalid utf-8: invalid UTF-8 in field {} near byte index {}",
280 self.field + 1, self.valid_up_to
281 )
282 }
283}
284
285/// `IntoInnerError` occurs when consuming a `Writer` fails.
286///
287/// Consuming the `Writer` causes a flush to happen. If the flush fails, then
288/// this error is returned, which contains both the original `Writer` and
289/// the error that occurred.
290///
291/// The type parameter `W` is the unconsumed writer.
292pub struct IntoInnerError<W> {
293 wtr: W,
294 err: io::Error,
295}
296
297impl<W> IntoInnerError<W> {
298 /// Creates a new `IntoInnerError`.
299 ///
300 /// (This is a visibility hack. It's public in this module, but not in the
301 /// crate.)
302 #[allow(dead_code)]
303 pub(crate) fn new(wtr: W, err: io::Error) -> IntoInnerError<W> {
304 IntoInnerError { wtr, err }
305 }
306
307 /// Returns reference to the i/o error which caused the failure.
308 ///
309 /// This error was returned when attempting to flush the writer internal buffer.
310 pub fn error(&self) -> &io::Error {
311 &self.err
312 }
313
314 /// Returns the i/o error which caused the failure.
315 ///
316 /// This error was returned when attempting to flush the writer internal buffer.
317 pub fn into_error(self) -> io::Error {
318 self.err
319 }
320
321 /// Returns the underlying writer which generated the error.
322 ///
323 /// The returned value can be used for error recovery, such as
324 /// re-inspecting the buffer.
325 pub fn into_writer(self) -> W {
326 self.wtr
327 }
328}
329
330impl<W: std::any::Any> StdError for IntoInnerError<W> {}
331
332impl<W> fmt::Display for IntoInnerError<W> {
333 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
334 self.err.fmt(f)
335 }
336}
337
338impl<W> fmt::Debug for IntoInnerError<W> {
339 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
340 self.err.fmt(f)
341 }
342}