use std::collections::BTreeMap;
use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::error::{OpsisError, Result};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Value {
Number(f64),
Text(String),
Bool(bool),
Null,
}
impl Value {
pub fn as_f64(&self) -> Option<f64> {
match self {
Value::Number(n) => Some(*n),
Value::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
Value::Text(s) => s.parse().ok(),
Value::Null => None,
}
}
pub fn as_str(&self) -> Option<String> {
match self {
Value::Text(s) => Some(s.clone()),
Value::Number(n) => Some(n.to_string()),
Value::Bool(b) => Some(b.to_string()),
Value::Null => None,
}
}
}
impl From<f64> for Value {
fn from(v: f64) -> Self { Value::Number(v) }
}
impl From<i64> for Value {
fn from(v: i64) -> Self { Value::Number(v as f64) }
}
impl From<&str> for Value {
fn from(v: &str) -> Self { Value::Text(v.to_string()) }
}
impl From<String> for Value {
fn from(v: String) -> Self { Value::Text(v) }
}
pub type Record = BTreeMap<String, Value>;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Dataset {
pub records: Vec<Record>,
}
impl Dataset {
pub fn new(records: Vec<Record>) -> Self { Self { records } }
pub fn is_empty(&self) -> bool { self.records.is_empty() }
pub fn len(&self) -> usize { self.records.len() }
pub fn fields(&self) -> Vec<String> {
let mut seen: Vec<String> = Vec::new();
for r in &self.records {
for k in r.keys() {
if !seen.contains(k) { seen.push(k.clone()); }
}
}
seen
}
pub fn column_f64(&self, field: &str) -> Result<Vec<f64>> {
if !self.fields().iter().any(|f| f == field) {
return Err(OpsisError::UnknownField {
field: field.into(),
available: self.fields(),
});
}
self.records
.iter()
.map(|r| {
r.get(field)
.and_then(|v| v.as_f64())
.ok_or(OpsisError::WrongFieldType {
field: field.into(),
wanted: "number",
})
})
.collect()
}
pub fn column_str(&self, field: &str) -> Result<Vec<String>> {
if !self.fields().iter().any(|f| f == field) {
return Err(OpsisError::UnknownField {
field: field.into(),
available: self.fields(),
});
}
Ok(self
.records
.iter()
.map(|r| r.get(field).and_then(|v| v.as_str()).unwrap_or_default())
.collect())
}
pub fn from_csv_path(path: impl AsRef<Path>) -> Result<Self> {
let mut rdr = csv::Reader::from_path(path)?;
let headers = rdr.headers()?.clone();
let mut records = Vec::new();
for row in rdr.records() {
let row = row?;
let mut rec = Record::new();
for (i, cell) in row.iter().enumerate() {
let key = headers.get(i).unwrap_or("").to_string();
let val = if let Ok(n) = cell.parse::<f64>() {
Value::Number(n)
} else if cell.is_empty() {
Value::Null
} else {
Value::Text(cell.to_string())
};
rec.insert(key, val);
}
records.push(rec);
}
Ok(Self { records })
}
pub fn from_json_str(s: &str) -> Result<Self> {
let records: Vec<Record> = serde_json::from_str(s)?;
Ok(Self { records })
}
}