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}