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}