Skip to main content

csv/
error.rs

1use core::fmt;
2
3extern crate alloc;
4#[cfg(feature = "serde")]
5use alloc::string::String;
6#[cfg(feature = "std")]
7use alloc::sync::Arc;
8
9/// Kinds of errors that can occur while reading CSV data.
10#[derive(Clone, Debug, PartialEq)]
11pub enum ReadErrorKind {
12    /// A quoted field was opened but never closed before end of input.
13    UnterminatedQuote,
14    /// Characters appeared after a closing quote before a delimiter or newline.
15    TrailingContent,
16    /// A field contained invalid UTF-8 bytes.
17    InvalidUtf8,
18    /// The number of fields differs from previous rows (strict mode).
19    InconsistentFieldCount { expected: usize, found: usize },
20    /// The configured maximum record size was exceeded.
21    ///
22    /// Only produced when [`Reader::set_max_record_bytes`](crate::Reader::set_max_record_bytes)
23    /// has been called. Reading stops at this point.
24    RecordTooLarge { limit: usize },
25    /// The configured maximum field size was exceeded.
26    ///
27    /// Only produced when [`Reader::set_max_field_bytes`](crate::Reader::set_max_field_bytes)
28    /// has been called.
29    FieldTooLarge { limit: usize },
30    /// The configured maximum column count was exceeded.
31    ///
32    /// Only produced when [`Reader::set_max_columns`](crate::Reader::set_max_columns)
33    /// has been called.
34    TooManyColumns { limit: usize },
35    /// The underlying [`Read`](crate::Read) source returned `Ok(true)` without
36    /// appending any bytes, which violates the [`Read`](crate::Read) contract.
37    ///
38    /// Reading stops at this point.
39    NoProgress,
40    /// A serde deserialization error occurred. Carries the error message.
41    #[cfg(feature = "serde")]
42    Deserialize(String),
43    /// An I/O error occurred while reading the underlying source.
44    Io,
45}
46
47/// An error returned when parsing a CSV row.
48///
49/// Includes the line number, the kind of error, and when applicable an
50/// inner source error (e.g. the original `std::io::Error`).
51///
52/// Cloning a `ReadError` preserves its source error, so the underlying cause
53/// survives the deferred-error path used by [`Row`](crate::Row) accessors.
54#[derive(Debug)]
55pub struct ReadError {
56    pub kind: ReadErrorKind,
57    /// 1-based line number on which the offending record started.
58    ///
59    /// A record that contains newlines inside quoted fields spans several
60    /// lines; this is the first of them. `0` when the position is not known
61    /// (for example an I/O error reported before any byte was parsed).
62    pub line: usize,
63    /// Byte offset within the record, currently always `0`.
64    pub column: usize,
65    #[cfg(feature = "std")]
66    source: Option<Arc<dyn core::error::Error + Send + Sync + 'static>>,
67}
68
69impl ReadError {
70    pub fn new(kind: ReadErrorKind, line: usize, column: usize) -> Self {
71        ReadError {
72            kind,
73            line,
74            column,
75            #[cfg(feature = "std")]
76            source: None,
77        }
78    }
79
80    pub fn kind(&self) -> &ReadErrorKind {
81        &self.kind
82    }
83
84    /// The inner source error, if any (e.g. the original `std::io::Error`).
85    ///
86    /// Shareable, so the error can be kept around after the [`ReadError`] that
87    /// carried it is cloned or dropped.
88    #[cfg(feature = "std")]
89    pub fn into_source(self) -> Option<Arc<dyn core::error::Error + Send + Sync + 'static>> {
90        self.source
91    }
92}
93
94impl Clone for ReadError {
95    fn clone(&self) -> Self {
96        ReadError {
97            kind: self.kind.clone(),
98            line: self.line,
99            column: self.column,
100            #[cfg(feature = "std")]
101            source: self.source.clone(),
102        }
103    }
104}
105
106impl fmt::Display for ReadError {
107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108        match &self.kind {
109            ReadErrorKind::UnterminatedQuote => {
110                write!(f, "unterminated quote at line {}, column {}", self.line, self.column)
111            }
112            ReadErrorKind::TrailingContent => {
113                write!(
114                    f,
115                    "trailing content after quoted field at line {}, column {}",
116                    self.line, self.column
117                )
118            }
119            ReadErrorKind::InvalidUtf8 => {
120                write!(f, "invalid UTF-8 at line {}, column {}", self.line, self.column)
121            }
122            ReadErrorKind::InconsistentFieldCount {
123                expected,
124                found,
125            } => {
126                write!(
127                    f,
128                    "expected {expected} fields, found {found} at line {}, column {}",
129                    self.line, self.column
130                )
131            }
132            ReadErrorKind::RecordTooLarge {
133                limit,
134            } => {
135                write!(
136                    f,
137                    "record larger than {limit} bytes at line {}, column {}",
138                    self.line, self.column
139                )
140            }
141            ReadErrorKind::FieldTooLarge {
142                limit,
143            } => {
144                write!(
145                    f,
146                    "field larger than {limit} bytes at line {}, column {}",
147                    self.line, self.column
148                )
149            }
150            ReadErrorKind::TooManyColumns {
151                limit,
152            } => {
153                write!(f, "more than {limit} columns at line {}, column {}", self.line, self.column)
154            }
155            ReadErrorKind::NoProgress => {
156                write!(
157                    f,
158                    "read source reported more data but appended none at line {}, column {}",
159                    self.line, self.column
160                )
161            }
162            #[cfg(feature = "serde")]
163            ReadErrorKind::Deserialize(msg) => {
164                write!(f, "deserialization error at line {}, column {}: {msg}", self.line, self.column)
165            }
166            ReadErrorKind::Io => {
167                write!(f, "I/O error at line {}, column {}", self.line, self.column)?;
168                #[cfg(feature = "std")]
169                if let Some(source) = &self.source {
170                    write!(f, ": {source}")?;
171                }
172                Ok(())
173            }
174        }
175    }
176}
177
178impl core::error::Error for ReadError {
179    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
180        #[cfg(feature = "std")]
181        {
182            self.source
183                .as_ref()
184                .map(|b| b.as_ref() as &(dyn core::error::Error + 'static))
185        }
186        #[cfg(not(feature = "std"))]
187        {
188            None
189        }
190    }
191}
192
193#[cfg(feature = "std")]
194impl From<std::io::Error> for ReadError {
195    fn from(e: std::io::Error) -> Self {
196        ReadError {
197            kind: ReadErrorKind::Io,
198            line: 0,
199            column: 0,
200            source: Some(Arc::new(e)),
201        }
202    }
203}
204
205/// An error returned when writing CSV data.
206#[derive(Debug)]
207pub enum WriteError {
208    /// The number of fields in a row differs from previous rows.
209    InconsistentFieldCount { expected: usize, found: usize, row: usize },
210    /// A second attempt was made to write headers after they've already been written.
211    HeadersAlreadyWritten,
212    /// A serde serialization error occurred.
213    #[cfg(feature = "serde")]
214    Serialize(String),
215    /// An I/O error occurred while writing.
216    #[cfg(feature = "std")]
217    Io(std::io::Error),
218    /// An I/O error occurred while writing (no_std).
219    #[cfg(not(feature = "std"))]
220    Io,
221}
222
223impl fmt::Display for WriteError {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        match self {
226            WriteError::InconsistentFieldCount {
227                expected,
228                found,
229                row,
230            } => {
231                write!(f, "expected {expected} fields, found {found} in row {row}")
232            }
233            WriteError::HeadersAlreadyWritten => write!(f, "headers have already been written"),
234            #[cfg(feature = "serde")]
235            WriteError::Serialize(msg) => write!(f, "serialization error: {msg}"),
236            #[cfg(feature = "std")]
237            WriteError::Io(e) => write!(f, "I/O error: {e}"),
238            #[cfg(not(feature = "std"))]
239            WriteError::Io => write!(f, "I/O error"),
240        }
241    }
242}
243
244impl core::error::Error for WriteError {
245    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
246        match self {
247            #[cfg(feature = "std")]
248            WriteError::Io(e) => Some(e),
249            _ => None,
250        }
251    }
252}
253
254#[cfg(feature = "std")]
255impl From<std::io::Error> for WriteError {
256    fn from(e: std::io::Error) -> Self {
257        WriteError::Io(e)
258    }
259}