use core::fmt;
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CsvLimitKind {
InputBytes,
Records,
FieldsPerRecord,
FieldBytes,
}
impl CsvLimitKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::InputBytes => "input bytes",
Self::Records => "records",
Self::FieldsPerRecord => "fields per record",
Self::FieldBytes => "field bytes",
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CsvErrorKind {
BareCarriageReturn,
QuoteInUnquotedField,
UnterminatedQuotedField,
TextAfterQuotedField,
FieldCountMismatch {
expected: usize,
found: usize,
},
LimitExceeded(CsvLimitKind),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CsvError {
kind: CsvErrorKind,
offset: usize,
line: usize,
column: usize,
record: usize,
field: usize,
}
impl CsvError {
pub(crate) const fn new(
kind: CsvErrorKind,
offset: usize,
line: usize,
column: usize,
record: usize,
field: usize,
) -> Self {
Self {
kind,
offset,
line,
column,
record,
field,
}
}
pub const fn kind(&self) -> CsvErrorKind {
self.kind
}
pub const fn offset(&self) -> usize {
self.offset
}
pub const fn line(&self) -> usize {
self.line
}
pub const fn column(&self) -> usize {
self.column
}
pub const fn record(&self) -> usize {
self.record
}
pub const fn field(&self) -> usize {
self.field
}
}
impl fmt::Display for CsvError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.kind {
CsvErrorKind::BareCarriageReturn => {
f.write_str("carriage return not followed by line feed")?
}
CsvErrorKind::QuoteInUnquotedField => f.write_str("quote inside an unquoted field")?,
CsvErrorKind::UnterminatedQuotedField => f.write_str("unterminated quoted field")?,
CsvErrorKind::TextAfterQuotedField => {
f.write_str("unexpected text after a quoted field")?
}
CsvErrorKind::FieldCountMismatch { expected, found } => write!(
f,
"record has {found} field(s) but {expected} were expected"
)?,
CsvErrorKind::LimitExceeded(limit) => write!(f, "limit exceeded: {}", limit.as_str())?,
}
write!(
f,
" at byte {}, line {}, column {} (record {}, field {})",
self.offset, self.line, self.column, self.record, self.field
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for CsvError {}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CsvDecodeErrorKind {
FieldCount,
Field,
HeaderMismatch,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CsvDecodeError {
kind: CsvDecodeErrorKind,
message: &'static str,
record: Option<usize>,
field: Option<usize>,
}
impl CsvDecodeError {
pub const fn new(kind: CsvDecodeErrorKind, message: &'static str) -> Self {
Self {
kind,
message,
record: None,
field: None,
}
}
pub const fn field_count() -> Self {
Self::new(
CsvDecodeErrorKind::FieldCount,
"record has the wrong number of fields for the target type",
)
}
pub const fn field(message: &'static str) -> Self {
Self::new(CsvDecodeErrorKind::Field, message)
}
pub const fn header_mismatch() -> Self {
Self::new(
CsvDecodeErrorKind::HeaderMismatch,
"header row does not match the target type's header",
)
}
pub const fn kind(&self) -> CsvDecodeErrorKind {
self.kind
}
pub const fn message(&self) -> &'static str {
self.message
}
pub const fn record(&self) -> Option<usize> {
self.record
}
pub const fn field_index(&self) -> Option<usize> {
self.field
}
pub const fn at_record(mut self, record: usize) -> Self {
self.record = Some(record);
self
}
pub const fn at_field(mut self, field: usize) -> Self {
self.field = Some(field);
self
}
}
impl fmt::Display for CsvDecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.message)?;
match (self.record, self.field) {
(Some(record), Some(field)) => write!(f, " (record {record}, field {field})"),
(Some(record), None) => write!(f, " (record {record})"),
(None, Some(field)) => write!(f, " (field {field})"),
(None, None) => Ok(()),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for CsvDecodeError {}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CsvFromStrError {
Parse(CsvError),
Decode(CsvDecodeError),
}
impl fmt::Display for CsvFromStrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Parse(error) => write!(f, "invalid CSV: {error}"),
Self::Decode(error) => write!(f, "CSV did not match the target type: {error}"),
}
}
}
impl From<CsvError> for CsvFromStrError {
fn from(error: CsvError) -> Self {
Self::Parse(error)
}
}
impl From<CsvDecodeError> for CsvFromStrError {
fn from(error: CsvDecodeError) -> Self {
Self::Decode(error)
}
}
#[cfg(feature = "std")]
impl std::error::Error for CsvFromStrError {}