128 lines
3.8 KiB
Rust
128 lines
3.8 KiB
Rust
|
use std::{fmt::Write, marker::PhantomData, sync::Arc};
|
||
|
|
||
|
use crate::extract::{DataPoint, DataSet};
|
||
|
|
||
|
const DATE_DISPLAY_FORMAT: &[time::format_description::FormatItem] =
|
||
|
time::macros::format_description!("[year]-[month]-[day]");
|
||
|
|
||
|
type SerializationChunk = Result<String, std::fmt::Error>;
|
||
|
|
||
|
pub trait DataFormat {
|
||
|
fn header(dataset: &DataSet) -> SerializationChunk;
|
||
|
fn row(dataset: &DataSet, row: &DataPoint) -> SerializationChunk;
|
||
|
const ROW_SEPARATOR: &'static str;
|
||
|
const END: &'static str;
|
||
|
}
|
||
|
|
||
|
pub struct DataSerializer<F: DataFormat> {
|
||
|
dataset: Arc<DataSet>,
|
||
|
index: Option<usize>,
|
||
|
serializer: PhantomData<F>,
|
||
|
}
|
||
|
|
||
|
impl<F: DataFormat> DataSerializer<F> {
|
||
|
pub fn new(dataset: Arc<DataSet>, _: F) -> Self {
|
||
|
Self {
|
||
|
dataset,
|
||
|
index: None,
|
||
|
serializer: PhantomData,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl<F: DataFormat> Iterator for DataSerializer<F> {
|
||
|
type Item = SerializationChunk;
|
||
|
fn next(&mut self) -> Option<Self::Item> {
|
||
|
match self.index {
|
||
|
None => {
|
||
|
self.index = Some(0);
|
||
|
let header = F::header(&self.dataset);
|
||
|
if self.dataset.rows.is_empty() {
|
||
|
Some(header.map(|s| s + F::END))
|
||
|
} else {
|
||
|
Some(header)
|
||
|
}
|
||
|
}
|
||
|
Some(i) => {
|
||
|
if let Some(row) = self.dataset.rows.get(i) {
|
||
|
self.index = Some(i + 1);
|
||
|
let serialized_row = F::row(&self.dataset, row);
|
||
|
let suffix = if i == self.dataset.rows.len() - 1 {
|
||
|
F::END
|
||
|
} else {
|
||
|
F::ROW_SEPARATOR
|
||
|
};
|
||
|
Some(serialized_row.map(|s| s + suffix))
|
||
|
} else {
|
||
|
None
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub struct Csv;
|
||
|
impl DataFormat for Csv {
|
||
|
fn header(dataset: &DataSet) -> SerializationChunk {
|
||
|
let mut header = String::from("Date");
|
||
|
for column in dataset.columns.iter() {
|
||
|
write!(&mut header, ",{}", column)?;
|
||
|
}
|
||
|
writeln!(&mut header)?;
|
||
|
Ok(header)
|
||
|
}
|
||
|
|
||
|
fn row(dataset: &DataSet, datapoint: &DataPoint) -> SerializationChunk {
|
||
|
let mut csv_row = datapoint
|
||
|
.date
|
||
|
.format(DATE_DISPLAY_FORMAT)
|
||
|
.expect("Failed to format date!");
|
||
|
for column in dataset.columns.iter() {
|
||
|
if let Some(val) = datapoint.values.get(column) {
|
||
|
write!(&mut csv_row, ",{}", val)?;
|
||
|
} else {
|
||
|
write!(&mut csv_row, ",")?;
|
||
|
}
|
||
|
}
|
||
|
writeln!(&mut csv_row)?;
|
||
|
Ok(csv_row)
|
||
|
}
|
||
|
|
||
|
const ROW_SEPARATOR: &'static str = "";
|
||
|
const END: &'static str = "";
|
||
|
}
|
||
|
|
||
|
pub struct Json;
|
||
|
impl DataFormat for Json {
|
||
|
fn header(dataset: &DataSet) -> SerializationChunk {
|
||
|
let mut header = String::from(r#"{"columns":["Date""#);
|
||
|
for column in dataset.columns.iter() {
|
||
|
write!(&mut header, ",{}", json::stringify(column.as_str()))?;
|
||
|
}
|
||
|
write!(&mut header, r#"],"rows":["#)?;
|
||
|
Ok(header)
|
||
|
}
|
||
|
|
||
|
fn row(dataset: &DataSet, datapoint: &DataPoint) -> SerializationChunk {
|
||
|
let mut row = String::from(r#"{"Date":"#);
|
||
|
write!(
|
||
|
&mut row,
|
||
|
r#""{}""#,
|
||
|
datapoint
|
||
|
.date
|
||
|
.format(DATE_DISPLAY_FORMAT)
|
||
|
.expect("Failed to format date!")
|
||
|
)?;
|
||
|
for column in dataset.columns.iter() {
|
||
|
if let Some(val) = datapoint.values.get(column) {
|
||
|
write!(&mut row, ",{}:{}", json::stringify(column.as_str()), val)?;
|
||
|
}
|
||
|
}
|
||
|
row += "}";
|
||
|
Ok(row)
|
||
|
}
|
||
|
|
||
|
const ROW_SEPARATOR: &'static str = ",";
|
||
|
const END: &'static str = "]}";
|
||
|
}
|