1use core::fmt;
4
5#[non_exhaustive]
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum CsvLimitKind {
11 InputBytes,
13 Records,
15 FieldsPerRecord,
17 FieldBytes,
19}
20
21impl CsvLimitKind {
22 pub const fn as_str(self) -> &'static str {
24 match self {
25 Self::InputBytes => "input bytes",
26 Self::Records => "records",
27 Self::FieldsPerRecord => "fields per record",
28 Self::FieldBytes => "field bytes",
29 }
30 }
31}
32
33#[non_exhaustive]
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum CsvErrorKind {
43 BareCarriageReturn,
46 QuoteInUnquotedField,
48 UnterminatedQuotedField,
50 TextAfterQuotedField,
53 FieldCountMismatch {
56 expected: usize,
58 found: usize,
60 },
61 LimitExceeded(CsvLimitKind),
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct CsvError {
71 kind: CsvErrorKind,
72 offset: usize,
73 line: usize,
74 column: usize,
75 record: usize,
76 field: usize,
77}
78
79impl CsvError {
80 pub(crate) const fn new(
81 kind: CsvErrorKind,
82 offset: usize,
83 line: usize,
84 column: usize,
85 record: usize,
86 field: usize,
87 ) -> Self {
88 Self {
89 kind,
90 offset,
91 line,
92 column,
93 record,
94 field,
95 }
96 }
97
98 pub const fn kind(&self) -> CsvErrorKind {
100 self.kind
101 }
102
103 pub const fn offset(&self) -> usize {
105 self.offset
106 }
107
108 pub const fn line(&self) -> usize {
110 self.line
111 }
112
113 pub const fn column(&self) -> usize {
115 self.column
116 }
117
118 pub const fn record(&self) -> usize {
120 self.record
121 }
122
123 pub const fn field(&self) -> usize {
125 self.field
126 }
127}
128
129impl fmt::Display for CsvError {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 match self.kind {
132 CsvErrorKind::BareCarriageReturn => {
133 f.write_str("carriage return not followed by line feed")?
134 }
135 CsvErrorKind::QuoteInUnquotedField => f.write_str("quote inside an unquoted field")?,
136 CsvErrorKind::UnterminatedQuotedField => f.write_str("unterminated quoted field")?,
137 CsvErrorKind::TextAfterQuotedField => {
138 f.write_str("unexpected text after a quoted field")?
139 }
140 CsvErrorKind::FieldCountMismatch { expected, found } => write!(
141 f,
142 "record has {found} field(s) but {expected} were expected"
143 )?,
144 CsvErrorKind::LimitExceeded(limit) => write!(f, "limit exceeded: {}", limit.as_str())?,
145 }
146 write!(
147 f,
148 " at byte {}, line {}, column {} (record {}, field {})",
149 self.offset, self.line, self.column, self.record, self.field
150 )
151 }
152}
153
154#[cfg(feature = "std")]
155impl std::error::Error for CsvError {}
156
157#[non_exhaustive]
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162pub enum CsvDecodeErrorKind {
163 FieldCount,
165 Field,
167 HeaderMismatch,
169}
170
171#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub struct CsvDecodeError {
177 kind: CsvDecodeErrorKind,
178 message: &'static str,
179 record: Option<usize>,
180 field: Option<usize>,
181}
182
183impl CsvDecodeError {
184 pub const fn new(kind: CsvDecodeErrorKind, message: &'static str) -> Self {
186 Self {
187 kind,
188 message,
189 record: None,
190 field: None,
191 }
192 }
193
194 pub const fn field_count() -> Self {
198 Self::new(
199 CsvDecodeErrorKind::FieldCount,
200 "record has the wrong number of fields for the target type",
201 )
202 }
203
204 pub const fn field(message: &'static str) -> Self {
206 Self::new(CsvDecodeErrorKind::Field, message)
207 }
208
209 pub const fn header_mismatch() -> Self {
211 Self::new(
212 CsvDecodeErrorKind::HeaderMismatch,
213 "header row does not match the target type's header",
214 )
215 }
216
217 pub const fn kind(&self) -> CsvDecodeErrorKind {
219 self.kind
220 }
221
222 pub const fn message(&self) -> &'static str {
224 self.message
225 }
226
227 pub const fn record(&self) -> Option<usize> {
229 self.record
230 }
231
232 pub const fn field_index(&self) -> Option<usize> {
234 self.field
235 }
236
237 pub const fn at_record(mut self, record: usize) -> Self {
239 self.record = Some(record);
240 self
241 }
242
243 pub const fn at_field(mut self, field: usize) -> Self {
245 self.field = Some(field);
246 self
247 }
248}
249
250impl fmt::Display for CsvDecodeError {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 f.write_str(self.message)?;
253 match (self.record, self.field) {
254 (Some(record), Some(field)) => write!(f, " (record {record}, field {field})"),
255 (Some(record), None) => write!(f, " (record {record})"),
256 (None, Some(field)) => write!(f, " (field {field})"),
257 (None, None) => Ok(()),
258 }
259 }
260}
261
262#[cfg(feature = "std")]
263impl std::error::Error for CsvDecodeError {}
264
265#[non_exhaustive]
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub enum CsvFromStrError {
272 Parse(CsvError),
274 Decode(CsvDecodeError),
276}
277
278impl fmt::Display for CsvFromStrError {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 match self {
281 Self::Parse(error) => write!(f, "invalid CSV: {error}"),
282 Self::Decode(error) => write!(f, "CSV did not match the target type: {error}"),
283 }
284 }
285}
286
287impl From<CsvError> for CsvFromStrError {
288 fn from(error: CsvError) -> Self {
289 Self::Parse(error)
290 }
291}
292
293impl From<CsvDecodeError> for CsvFromStrError {
294 fn from(error: CsvDecodeError) -> Self {
295 Self::Decode(error)
296 }
297}
298
299#[cfg(feature = "std")]
300impl std::error::Error for CsvFromStrError {}