#![cfg_attr(not(test), warn(missing_docs))]
mod error;
pub mod from_csv;
mod to_csv;
pub use error::{CsvError, Result};
pub use from_csv::{
from_csv,
from_csv_reader,
from_csv_reader_with_config,
from_csv_with_config,
FromCsvConfig,
DEFAULT_MAX_CELL_SIZE,
DEFAULT_MAX_COLUMNS,
DEFAULT_MAX_HEADER_SIZE,
DEFAULT_MAX_ROWS,
DEFAULT_MAX_TOTAL_SIZE,
};
pub use to_csv::{
to_csv, to_csv_list, to_csv_list_with_config, to_csv_list_writer,
to_csv_list_writer_with_config, to_csv_with_config, to_csv_writer, to_csv_writer_with_config,
ToCsvConfig,
};
#[cfg(test)]
mod integration_tests {
use super::*;
use hedl_core::{Document, Item, MatrixList, Node, Value};
use hedl_test::expr_value;
#[test]
fn test_round_trip_conversion() {
let mut doc = Document::new((1, 0));
let mut list = MatrixList::new(
"Person",
vec![
"id".to_string(),
"name".to_string(),
"age".to_string(),
"score".to_string(),
"active".to_string(),
],
);
list.add_row(Node::new(
"Person",
"1",
vec![
Value::String("1".to_string().into()),
Value::String("Alice".to_string().into()),
Value::Int(30),
Value::Float(95.5),
Value::Bool(true),
],
));
list.add_row(Node::new(
"Person",
"2",
vec![
Value::String("2".to_string().into()),
Value::String("Bob".to_string().into()),
Value::Int(25),
Value::Float(87.3),
Value::Bool(false),
],
));
doc.root.insert("people".to_string(), Item::List(list));
let csv = to_csv(&doc).unwrap();
let doc2 = from_csv(&csv, "Person", &["name", "age", "score", "active"]).unwrap();
let list2 = doc2.get("persons").unwrap().as_list().unwrap();
assert_eq!(list2.rows.len(), 2);
let row1 = &list2.rows[0];
assert_eq!(&*row1.id, "1");
assert_eq!(row1.fields[0], Value::Int(1)); assert_eq!(row1.fields[1], Value::String("Alice".to_string().into()));
assert_eq!(row1.fields[2], Value::Int(30));
assert_eq!(row1.fields[3], Value::Float(95.5));
assert_eq!(row1.fields[4], Value::Bool(true));
let row2 = &list2.rows[1];
assert_eq!(&*row2.id, "2");
assert_eq!(row2.fields[0], Value::Int(2)); assert_eq!(row2.fields[1], Value::String("Bob".to_string().into()));
assert_eq!(row2.fields[2], Value::Int(25));
assert_eq!(row2.fields[3], Value::Float(87.3));
assert_eq!(row2.fields[4], Value::Bool(false));
}
#[test]
fn test_null_values() {
let mut doc = Document::new((1, 0));
let mut list = MatrixList::new("Item", vec!["id".to_string(), "value".to_string()]);
list.add_row(Node::new(
"Item",
"1",
vec![Value::String("1".to_string().into()), Value::Null],
));
doc.root.insert("items".to_string(), Item::List(list));
let csv = to_csv(&doc).unwrap();
let doc2 = from_csv(&csv, "Item", &["value"]).unwrap();
let list2 = doc2.get("items").unwrap().as_list().unwrap();
assert_eq!(list2.rows[0].fields[0], Value::Int(1)); assert_eq!(list2.rows[0].fields[1], Value::Null);
}
#[test]
fn test_references() {
let mut doc = Document::new((1, 0));
let mut list = MatrixList::new("Item", vec!["id".to_string(), "ref".to_string()]);
list.add_row(Node::new(
"Item",
"1",
vec![
Value::String("1".to_string().into()),
Value::Reference(hedl_core::Reference::local("user1")),
],
));
list.add_row(Node::new(
"Item",
"2",
vec![
Value::String("2".to_string().into()),
Value::Reference(hedl_core::Reference::qualified("User", "user2")),
],
));
doc.root.insert("items".to_string(), Item::List(list));
let csv = to_csv(&doc).unwrap();
let doc2 = from_csv(&csv, "Item", &["ref"]).unwrap();
let list2 = doc2.get("items").unwrap().as_list().unwrap();
assert_eq!(list2.rows[0].fields[0], Value::Int(1)); let ref1 = list2.rows[0].fields[1].as_reference().unwrap();
assert_eq!(&*ref1.id, "user1");
assert_eq!(ref1.type_name, None);
assert_eq!(list2.rows[1].fields[0], Value::Int(2)); let ref2 = list2.rows[1].fields[1].as_reference().unwrap();
assert_eq!(&*ref2.id, "user2");
assert_eq!(ref2.type_name.as_deref(), Some("User"));
}
#[test]
fn test_mixed_types() {
let csv_data = r"
id,value
1,42
2,3.25
3,true
4,hello
5,@ref1
6,
";
let doc = from_csv(csv_data, "Item", &["value"]).unwrap();
let list = doc.get("items").unwrap().as_list().unwrap();
assert_eq!(list.rows.len(), 6);
assert_eq!(list.rows[0].fields[0], Value::Int(1)); assert_eq!(list.rows[0].fields[1], Value::Int(42));
assert_eq!(list.rows[1].fields[0], Value::Int(2)); assert_eq!(list.rows[1].fields[1], Value::Float(3.25));
assert_eq!(list.rows[2].fields[0], Value::Int(3)); assert_eq!(list.rows[2].fields[1], Value::Bool(true));
assert_eq!(list.rows[3].fields[0], Value::Int(4)); assert_eq!(
list.rows[3].fields[1],
Value::String("hello".to_string().into())
);
assert_eq!(list.rows[4].fields[0], Value::Int(5)); assert!(matches!(list.rows[4].fields[1], Value::Reference(_)));
assert_eq!(list.rows[5].fields[0], Value::Int(6)); assert_eq!(list.rows[5].fields[1], Value::Null);
}
#[test]
fn test_expressions() {
let mut doc = Document::new((1, 0));
let mut list = MatrixList::new("Item", vec!["id".to_string(), "expr".to_string()]);
list.add_row(Node::new(
"Item",
"1",
vec![
Value::String("1".to_string().into()),
expr_value("add(x, y)"),
],
));
doc.root.insert("items".to_string(), Item::List(list));
let csv = to_csv(&doc).unwrap();
assert!(csv.contains("$(add(x, y))"));
let doc2 = from_csv(&csv, "Item", &["expr"]).unwrap();
let list2 = doc2.get("items").unwrap().as_list().unwrap();
assert_eq!(list2.rows[0].fields[0], Value::Int(1)); assert_eq!(list2.rows[0].fields[1], expr_value("add(x, y)"));
}
}