Skip to main content

csv/
error.rs

1use core::fmt;
2
3extern crate alloc;
4#[cfg(feature = "std")]
5use alloc::boxed::Box;
6#[cfg(feature = "serde")]
7use alloc::string::String;
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    /// A serde deserialization error occurred. Carries the error message.
21    #[cfg(feature = "serde")]
22    Deserialize(String),
23    /// An I/O error occurred while reading the underlying source.
24    Io,
25}
26
27/// An error returned when parsing a CSV row.
28///
29/// Includes the line number, the kind of error, and when applicable an
30/// inner source error (e.g. the original `std::io::Error`).
31#[derive(Debug)]
32pub struct ReadError {
33    pub kind: ReadErrorKind,
34    pub line: usize,
35    pub column: usize,
36    #[cfg(feature = "std")]
37    source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
38}
39
40impl ReadError {
41    pub fn new(kind: ReadErrorKind, line: usize, column: usize) -> Self {
42        ReadError {
43            kind,
44            line,
45            column,
46            #[cfg(feature = "std")]
47            source: None,
48        }
49    }
50
51    pub fn kind(&self) -> &ReadErrorKind {
52        &self.kind
53    }
54
55    /// The inner source error, if any (e.g. the original `std::io::Error` or serde error).
56    #[cfg(feature = "std")]
57    pub fn into_source(self) -> Option<Box<dyn std::error::Error + Send + Sync + 'static>> {
58        self.source
59    }
60}
61
62impl Clone for ReadError {
63    fn clone(&self) -> Self {
64        ReadError {
65            kind: self.kind.clone(),
66            line: self.line,
67            column: self.column,
68            #[cfg(feature = "std")]
69            source: None,
70        }
71    }
72}
73
74impl fmt::Display for ReadError {
75    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76        match &self.kind {
77            ReadErrorKind::UnterminatedQuote => {
78                write!(f, "unterminated quote at line {}, column {}", self.line, self.column)
79            }
80            ReadErrorKind::TrailingContent => {
81                write!(
82                    f,
83                    "trailing content after quoted field at line {}, column {}",
84                    self.line, self.column
85                )
86            }
87            ReadErrorKind::InvalidUtf8 => {
88                write!(f, "invalid UTF-8 at line {}, column {}", self.line, self.column)
89            }
90            ReadErrorKind::InconsistentFieldCount {
91                expected,
92                found,
93            } => {
94                write!(
95                    f,
96                    "expected {expected} fields, found {found} at line {}, column {}",
97                    self.line, self.column
98                )
99            }
100            #[cfg(feature = "serde")]
101            ReadErrorKind::Deserialize(msg) => {
102                write!(f, "deserialization error at line {}, column {}: {msg}", self.line, self.column)
103            }
104            ReadErrorKind::Io => {
105                write!(f, "I/O error at line {}, column {}", self.line, self.column)
106            }
107        }
108    }
109}
110
111#[cfg(feature = "std")]
112impl std::error::Error for ReadError {
113    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
114        self.source
115            .as_ref()
116            .map(|b| b.as_ref() as &(dyn std::error::Error + 'static))
117    }
118}
119
120#[cfg(all(not(feature = "std"), feature = "serde"))]
121impl core::error::Error for ReadError {}
122
123#[cfg(feature = "std")]
124impl From<std::io::Error> for ReadError {
125    fn from(e: std::io::Error) -> Self {
126        ReadError {
127            kind: ReadErrorKind::Io,
128            line: 0,
129            column: 0,
130            source: Some(Box::new(e)),
131        }
132    }
133}
134
135/// An error returned when writing CSV data.
136#[derive(Debug)]
137pub enum WriteError {
138    /// The number of fields in a row differs from previous rows.
139    InconsistentFieldCount { expected: usize, found: usize, row: usize },
140    /// A second attempt was made to write headers after they've already been written.
141    HeadersAlreadyWritten,
142    /// A serde serialization error occurred.
143    #[cfg(feature = "serde")]
144    Serialize(String),
145    /// An I/O error occurred while writing.
146    #[cfg(feature = "std")]
147    Io(std::io::Error),
148    /// An I/O error occurred while writing (no_std).
149    #[cfg(not(feature = "std"))]
150    Io,
151}
152
153impl fmt::Display for WriteError {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        match self {
156            WriteError::InconsistentFieldCount {
157                expected,
158                found,
159                row,
160            } => {
161                write!(f, "expected {expected} fields, found {found} in row {row}")
162            }
163            WriteError::HeadersAlreadyWritten => write!(f, "headers have already been written"),
164            #[cfg(feature = "serde")]
165            WriteError::Serialize(msg) => write!(f, "serialization error: {msg}"),
166            #[cfg(feature = "std")]
167            WriteError::Io(e) => write!(f, "I/O error: {e}"),
168            #[cfg(not(feature = "std"))]
169            WriteError::Io => write!(f, "I/O error"),
170        }
171    }
172}
173
174#[cfg(feature = "std")]
175impl std::error::Error for WriteError {
176    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
177        match self {
178            WriteError::Io(e) => Some(e),
179            _ => None,
180        }
181    }
182}
183
184#[cfg(not(feature = "std"))]
185impl core::error::Error for WriteError {}
186
187#[cfg(feature = "std")]
188impl From<std::io::Error> for WriteError {
189    fn from(e: std::io::Error) -> Self {
190        WriteError::Io(e)
191    }
192}