use alloc::string::String;
use alloc::vec::Vec;
use crate::error::{CsvDecodeError, CsvFromStrError};
use crate::limits::CsvLimits;
use crate::reader::read_str_with_limits;
use crate::writer::CsvWriter;
pub trait CsvEncode {
fn header() -> Vec<&'static str>;
fn encode_fields(&self, out: &mut Vec<String>);
}
pub trait CsvDecode: Sized {
fn decode_fields(fields: &[&str]) -> Result<Self, CsvDecodeError>;
}
pub fn to_csv_string<T: CsvEncode>(records: &[T]) -> String {
let mut writer = CsvWriter::new();
writer.write_record(T::header());
let mut fields: Vec<String> = Vec::new();
for record in records {
fields.clear();
record.encode_fields(&mut fields);
writer.write_record(&fields);
}
writer.into_string()
}
pub fn to_csv_string_headerless<T: CsvEncode>(records: &[T]) -> String {
let mut writer = CsvWriter::new();
let mut fields: Vec<String> = Vec::new();
for record in records {
fields.clear();
record.encode_fields(&mut fields);
writer.write_record(&fields);
}
writer.into_string()
}
pub fn from_csv_str<T: CsvEncode + CsvDecode>(input: &str) -> Result<Vec<T>, CsvFromStrError> {
from_csv_str_with_limits(input, &CsvLimits::conservative())
}
pub fn from_csv_str_with_limits<T: CsvEncode + CsvDecode>(
input: &str,
limits: &CsvLimits,
) -> Result<Vec<T>, CsvFromStrError> {
let records = read_str_with_limits(input, limits)?;
let mut rows = records.into_iter();
match rows.next() {
None => Ok(Vec::new()),
Some(header) => {
let expected = T::header();
if header.len() != expected.len()
|| header.iter().zip(expected.iter()).any(|(a, b)| a != b)
{
return Err(CsvDecodeError::header_mismatch().at_record(0).into());
}
decode_rows::<T>(rows, 1)
}
}
}
pub fn from_csv_str_headerless<T: CsvDecode>(input: &str) -> Result<Vec<T>, CsvFromStrError> {
from_csv_str_headerless_with_limits(input, &CsvLimits::conservative())
}
pub fn from_csv_str_headerless_with_limits<T: CsvDecode>(
input: &str,
limits: &CsvLimits,
) -> Result<Vec<T>, CsvFromStrError> {
let records = read_str_with_limits(input, limits)?;
decode_rows::<T>(records.into_iter(), 0)
}
fn decode_rows<T: CsvDecode>(
rows: impl Iterator<Item = Vec<String>>,
base_record: usize,
) -> Result<Vec<T>, CsvFromStrError> {
let mut out = Vec::new();
for (offset, record) in rows.enumerate() {
let refs: Vec<&str> = record.iter().map(String::as_str).collect();
let value =
T::decode_fields(&refs).map_err(|error| error.at_record(base_record + offset))?;
out.push(value);
}
Ok(out)
}