Skip to main content

matten_data/
error.rs

1//! Crate-local error type for `matten-data` (RFC-034 §6).
2//!
3//! `MattenDataError` is the single error type returned by every fallible
4//! `matten-data` API. It is crate-local: core `matten::MattenError` is wrapped
5//! (see [`MattenDataError::Matten`]) but never extended for table-specific
6//! failures (RFC-014, RFC-033 §10).
7
8use std::fmt;
9use std::path::PathBuf;
10
11/// Errors produced by `matten-data` table ingestion and conversion.
12///
13/// All external-input APIs return this type; malformed input never panics
14/// (RFC-035 §1). Error messages include row/column context where practical.
15/// Row numbers are **one-based CSV line numbers** (the header is line 1, so the
16/// first data row is line 2).
17#[derive(Debug)]
18#[non_exhaustive]
19pub enum MattenDataError {
20    /// A CSV structural problem reported by the parser or by header validation
21    /// (for example an empty header name).
22    Csv {
23        /// Human-readable description of the problem.
24        message: String,
25    },
26    /// An I/O error while reading a CSV path.
27    Io {
28        /// The path that failed to read.
29        path: PathBuf,
30        /// The underlying I/O error.
31        source: std::io::Error,
32    },
33    /// The input was empty (no header row).
34    EmptyInput,
35    /// A requested column name does not exist in the table.
36    MissingColumn {
37        /// The requested column name.
38        name: String,
39    },
40    /// The CSV header contains a duplicate column name.
41    DuplicateColumn {
42        /// The duplicated column name.
43        name: String,
44    },
45    /// The same column name was requested more than once in a selection.
46    DuplicateSelection {
47        /// The duplicated selection name.
48        name: String,
49    },
50    /// A data row has a different number of cells than the header.
51    RaggedRow {
52        /// One-based CSV line number of the offending row.
53        row: usize,
54        /// Number of columns expected (from the header).
55        expected: usize,
56        /// Number of cells actually found.
57        actual: usize,
58    },
59    /// A cell could not be converted to `f64` during numeric conversion.
60    NonNumericValue {
61        /// Column name.
62        column: String,
63        /// One-based CSV line number.
64        row: usize,
65        /// The offending cell value, rendered as text.
66        value: String,
67    },
68    /// A missing cell remained during numeric conversion (fill it first).
69    MissingValue {
70        /// Column name.
71        column: String,
72        /// One-based CSV line number.
73        row: usize,
74    },
75    /// A column selection or conversion was attempted with no columns.
76    EmptySelection,
77    /// A wrapped core `matten` error (for example from `Tensor` construction).
78    Matten(matten::MattenError),
79}
80
81impl fmt::Display for MattenDataError {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            MattenDataError::Csv { message } => {
85                write!(f, "matten-data CSV error: {message}")
86            }
87            MattenDataError::Io { path, source } => {
88                write!(
89                    f,
90                    "matten-data I/O error reading {}: {source}",
91                    path.display()
92                )
93            }
94            MattenDataError::EmptyInput => {
95                write!(
96                    f,
97                    "matten-data error: input is empty (a header row is required)"
98                )
99            }
100            MattenDataError::MissingColumn { name } => {
101                write!(f, "matten-data error: column \"{name}\" does not exist")
102            }
103            MattenDataError::DuplicateColumn { name } => {
104                write!(f, "matten-data error: duplicate header column \"{name}\"")
105            }
106            MattenDataError::DuplicateSelection { name } => {
107                write!(
108                    f,
109                    "matten-data error: column \"{name}\" was selected more than once"
110                )
111            }
112            MattenDataError::RaggedRow {
113                row,
114                expected,
115                actual,
116            } => write!(
117                f,
118                "matten-data error: row {row} has {actual} cells but the header has {expected} columns"
119            ),
120            MattenDataError::NonNumericValue { column, row, value } => write!(
121                f,
122                "matten-data numeric conversion error: column \"{column}\", row {row} contains \"{value}\", \
123                 which cannot be converted to f64. Fill or clean the column before calling try_numeric()."
124            ),
125            MattenDataError::MissingValue { column, row } => write!(
126                f,
127                "matten-data numeric conversion error: column \"{column}\", row {row} is missing. \
128                 Fill missing values (e.g. with fill_missing) before calling try_numeric()."
129            ),
130            MattenDataError::EmptySelection => {
131                write!(f, "matten-data error: no columns were selected")
132            }
133            MattenDataError::Matten(e) => write!(f, "matten-data error: {e}"),
134        }
135    }
136}
137
138impl std::error::Error for MattenDataError {
139    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
140        match self {
141            MattenDataError::Io { source, .. } => Some(source),
142            MattenDataError::Matten(e) => Some(e),
143            _ => None,
144        }
145    }
146}
147
148impl From<matten::MattenError> for MattenDataError {
149    fn from(e: matten::MattenError) -> Self {
150        MattenDataError::Matten(e)
151    }
152}