1use crate::error::{Error, Result};
6use serde::{Deserialize, Serialize};
7use std::path::Path;
8
9#[derive(Debug, Clone)]
11pub enum DataSource {
12 Table(Table),
14 Series(Vec<Series>),
16}
17
18impl DataSource {
19 pub fn from_csv(path: impl AsRef<Path>) -> Result<Self> {
25 let path = path.as_ref();
26 let mut reader = csv::Reader::from_path(path)?;
27
28 let headers: Vec<String> = reader.headers()?.iter().map(String::from).collect();
29
30 let mut rows = Vec::new();
31 for result in reader.records() {
32 let record = result?;
33 let row: Vec<Value> = record.iter().map(Value::parse).collect();
34 rows.push(row);
35 }
36
37 Ok(Self::Table(Table { headers, rows }))
38 }
39
40 pub fn from_csv_string(content: &str) -> Result<Self> {
46 let mut reader = csv::Reader::from_reader(content.as_bytes());
47
48 let headers: Vec<String> = reader.headers()?.iter().map(String::from).collect();
49
50 let mut rows = Vec::new();
51 for result in reader.records() {
52 let record = result?;
53 let row: Vec<Value> = record.iter().map(Value::parse).collect();
54 rows.push(row);
55 }
56
57 Ok(Self::Table(Table { headers, rows }))
58 }
59
60 pub fn from_json(content: &str) -> Result<Self> {
66 let value: serde_json::Value = serde_json::from_str(content)?;
67 Self::from_json_value(value)
68 }
69
70 pub fn from_json_value(value: serde_json::Value) -> Result<Self> {
76 match value {
77 serde_json::Value::Array(arr) => {
78 if arr.is_empty() {
79 return Err(Error::NoData);
80 }
81
82 if arr[0].is_object() {
84 let headers: Vec<String> =
85 arr[0].as_object().unwrap().keys().cloned().collect();
86
87 let rows: Vec<Vec<Value>> = arr
88 .iter()
89 .filter_map(|obj| {
90 obj.as_object().map(|o| {
91 headers
92 .iter()
93 .map(|h| Value::from_json(o.get(h).cloned()))
94 .collect()
95 })
96 })
97 .collect();
98
99 Ok(Self::Table(Table { headers, rows }))
100 } else {
101 let values: Vec<f64> = arr.iter().filter_map(|v| v.as_f64()).collect();
103
104 Ok(Self::Series(vec![Series {
105 name: "data".to_string(),
106 values,
107 }]))
108 }
109 }
110 _ => Err(Error::InvalidData {
111 message: "expected JSON array".to_string(),
112 }),
113 }
114 }
115
116 #[must_use]
118 pub fn from_points(points: Vec<(f64, f64)>) -> Self {
119 let headers = vec!["x".to_string(), "y".to_string()];
120 let rows: Vec<Vec<Value>> = points
121 .into_iter()
122 .map(|(x, y)| vec![Value::Number(x), Value::Number(y)])
123 .collect();
124
125 Self::Table(Table { headers, rows })
126 }
127
128 #[must_use]
130 pub fn as_table(&self) -> Option<&Table> {
131 match self {
132 Self::Table(t) => Some(t),
133 _ => None,
134 }
135 }
136
137 #[must_use]
139 pub fn as_series(&self) -> Option<&[Series]> {
140 match self {
141 Self::Series(s) => Some(s),
142 _ => None,
143 }
144 }
145}
146
147#[derive(Debug, Clone)]
149pub struct Table {
150 pub headers: Vec<String>,
152 pub rows: Vec<Vec<Value>>,
154}
155
156impl Table {
157 #[must_use]
159 pub fn column_index(&self, name: &str) -> Option<usize> {
160 self.headers.iter().position(|h| h == name)
161 }
162
163 #[must_use]
165 pub fn column_as_f64(&self, name: &str) -> Option<Vec<f64>> {
166 let idx = self.column_index(name)?;
167 Some(
168 self.rows
169 .iter()
170 .filter_map(|row| row.get(idx).and_then(Value::as_f64))
171 .collect(),
172 )
173 }
174
175 #[must_use]
177 pub fn column_as_str(&self, name: &str) -> Option<Vec<String>> {
178 let idx = self.column_index(name)?;
179 Some(
180 self.rows
181 .iter()
182 .filter_map(|row| row.get(idx).map(Value::to_string))
183 .collect(),
184 )
185 }
186
187 #[must_use]
189 pub fn row_count(&self) -> usize {
190 self.rows.len()
191 }
192
193 #[must_use]
195 pub fn column_count(&self) -> usize {
196 self.headers.len()
197 }
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct Series {
203 pub name: String,
205 pub values: Vec<f64>,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211pub enum Value {
212 Number(f64),
214 String(String),
216 Null,
218}
219
220impl Value {
221 #[must_use]
223 pub fn parse(s: &str) -> Self {
224 if s.is_empty() {
225 Self::Null
226 } else if let Ok(n) = s.parse::<f64>() {
227 Self::Number(n)
228 } else {
229 Self::String(s.to_string())
230 }
231 }
232
233 #[must_use]
235 pub fn from_json(value: Option<serde_json::Value>) -> Self {
236 match value {
237 Some(serde_json::Value::Number(n)) => Self::Number(n.as_f64().unwrap_or(0.0)),
238 Some(serde_json::Value::String(s)) => Self::String(s),
239 Some(serde_json::Value::Null) | None => Self::Null,
240 Some(v) => Self::String(v.to_string()),
241 }
242 }
243
244 #[must_use]
246 pub fn as_f64(&self) -> Option<f64> {
247 match self {
248 Self::Number(n) => Some(*n),
249 Self::String(s) => s.parse().ok(),
250 Self::Null => None,
251 }
252 }
253
254 #[must_use]
256 pub fn is_null(&self) -> bool {
257 matches!(self, Self::Null)
258 }
259}
260
261impl std::fmt::Display for Value {
262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263 match self {
264 Self::Number(n) => write!(f, "{n}"),
265 Self::String(s) => write!(f, "{s}"),
266 Self::Null => write!(f, ""),
267 }
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn parse_csv_string() {
277 let csv = "x,y\n1,10\n2,20\n3,30";
278 let data = DataSource::from_csv_string(csv).unwrap();
279
280 let table = data.as_table().unwrap();
281 assert_eq!(table.headers, vec!["x", "y"]);
282 assert_eq!(table.row_count(), 3);
283 }
284
285 #[test]
286 fn table_column_access() {
287 let csv = "name,value\nalpha,100\nbeta,200";
288 let data = DataSource::from_csv_string(csv).unwrap();
289 let table = data.as_table().unwrap();
290
291 assert_eq!(table.column_index("name"), Some(0));
292 assert_eq!(table.column_index("value"), Some(1));
293 assert_eq!(table.column_index("missing"), None);
294
295 let values = table.column_as_f64("value").unwrap();
296 assert_eq!(values, vec![100.0, 200.0]);
297 }
298
299 #[test]
300 fn from_json_array_of_objects() {
301 let json = r#"[
302 {"x": 1, "y": 10},
303 {"x": 2, "y": 20}
304 ]"#;
305
306 let data = DataSource::from_json(json).unwrap();
307 let table = data.as_table().unwrap();
308 assert_eq!(table.row_count(), 2);
309 }
310
311 #[test]
312 fn from_points() {
313 let data = DataSource::from_points(vec![(1.0, 10.0), (2.0, 20.0)]);
314 let table = data.as_table().unwrap();
315
316 assert_eq!(table.headers, vec!["x", "y"]);
317 assert_eq!(table.row_count(), 2);
318 }
319
320 #[test]
321 fn value_parsing() {
322 assert!(matches!(Value::parse("42"), Value::Number(n) if (n - 42.0).abs() < f64::EPSILON));
323 assert!(matches!(Value::parse("hello"), Value::String(_)));
324 assert!(matches!(Value::parse(""), Value::Null));
325 }
326
327 #[test]
328 fn value_as_f64() {
329 assert_eq!(Value::Number(42.0).as_f64(), Some(42.0));
330 assert_eq!(Value::String("42".to_string()).as_f64(), Some(42.0));
331 assert_eq!(Value::String("hello".to_string()).as_f64(), None);
332 assert_eq!(Value::Null.as_f64(), None);
333 }
334}