use std::io::Read;
use deser::io::{Reader, Writer};
use deser::{Deserialize, Serialize};
use deser_csv::{DeserializerConfig, Headers, Serializer, SerializerConfig, StreamDeserializer};
struct Chunked<'a> {
input: &'a [u8],
size: usize,
}
impl Read for Chunked<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let len = self.size.min(buf.len()).min(self.input.len());
buf[..len].copy_from_slice(&self.input[..len]);
self.input = &self.input[len..];
Ok(len)
}
}
fn chunk_sizes(len: usize) -> impl Iterator<Item = usize> {
(1..=len).filter(move |&size| !cfg!(miri) || size <= 3 || size.is_power_of_two() || size == len)
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
struct Row {
name: String,
age: u32,
}
fn row(name: &str, age: u32) -> Row {
Row {
name: name.into(),
age,
}
}
#[test]
fn test_read_in_chunks() {
let config = DeserializerConfig::builder()
.comment(Some(b'#'))
.sep_line(true)
.build();
let input =
"\u{feff}sep=;\r\n# comment\r\nname;age\r\n\r\n\"ja\r\nne\";42\r\"jo\"\"hn\";23\r\n\r\n";
for size in chunk_sizes(input.len()) {
let mut reader = config.reader(Chunked {
input: input.as_bytes(),
size,
});
assert_eq!(
reader.read::<Row>().unwrap(),
Some(row("ja\r\nne", 42)),
"size {}",
size
);
assert_eq!(reader.deserializer().headers().unwrap(), ["name", "age"]);
assert_eq!(reader.read::<Row>().unwrap(), Some(row("jo\"hn", 23)));
assert_eq!(reader.read::<Row>().unwrap(), None);
reader.end().unwrap();
}
}
#[test]
fn test_utf16_in_chunks() {
for input in [&b"\xff\xfea\x00,\x00b\x00"[..], b"\xfe\xff\x00a"] {
for size in chunk_sizes(input.len()) {
let mut reader = DeserializerConfig::new().reader(Chunked { input, size });
let err = reader.read::<Vec<String>>().unwrap_err();
assert_eq!(err.message(), "input is UTF-16, only UTF-8 is supported");
}
}
}
#[test]
fn test_errors_continue() {
let input = b"name,age\njane,42\njohn,x\n\"max\"x,1\nmoritz,1,2\nanna,7";
for size in chunk_sizes(input.len()) {
let mut reader = DeserializerConfig::new().reader(Chunked { input, size });
let mut results = Vec::new();
loop {
match reader.read::<Row>() {
Ok(Some(row)) => results.push(Ok(row.name)),
Ok(None) => break,
Err(err) => {
results.push(Err((err.message().to_string(), err.line(), err.column())))
}
}
}
assert_eq!(
results,
[
Ok("jane".into()),
Err(("invalid value \"x\", expected u32".into(), Some(3), Some(6))),
Err((
"unexpected character after a quoted field".into(),
Some(4),
Some(6)
)),
Err(("record has 3 fields, expected 2".into(), Some(5), Some(1))),
Ok("anna".into()),
],
"size {}",
size
);
}
}
#[test]
fn test_stream_errors_end_the_stream() {
let config = DeserializerConfig::builder().max_record_len(16).build();
let input = b"name,age\n\"this record never ends,1\njane,42\n";
let mut reader = config.reader(Chunked { input, size: 4 });
let err = reader.read::<Row>().unwrap_err();
assert_eq!(
err.message(),
"record is longer than the maximum of 16 bytes"
);
assert_eq!((err.line(), err.column()), (Some(2), Some(1)));
assert!(reader.read::<Row>().is_err());
}
#[test]
fn test_read_borrowed() {
#[derive(Debug, Deserialize, PartialEq)]
struct Row<'a> {
name: &'a str,
age: u32,
}
let mut reader = DeserializerConfig::new().reader(&b"name,age\njane,42\n"[..]);
let row: Row = reader.read_borrowed().unwrap().unwrap();
assert_eq!(
row,
Row {
name: "jane",
age: 42
}
);
}
#[test]
fn test_reader_with_headers() {
let de = StreamDeserializer::with_headers(DeserializerConfig::new(), ["name", "age"]);
let mut reader = Reader::new(&b"jane,42\n"[..], de);
assert_eq!(reader.read::<Row>().unwrap(), Some(row("jane", 42)));
let config = DeserializerConfig::builder().headers(Headers::None).build();
let mut reader = config.reader(&b"jane,42\n"[..]);
assert_eq!(
reader.read::<(String, u32)>().unwrap(),
Some(("jane".into(), 42))
);
assert_eq!(reader.deserializer().headers(), None);
}
#[test]
fn test_writer() {
let mut writer = SerializerConfig::new().writer(Vec::new());
writer.write(&row("jane", 42)).unwrap();
assert!(writer.write(&42).is_err());
writer.write(&row("john,jr", 23)).unwrap();
assert_eq!(writer.serializer().headers().unwrap(), ["name", "age"]);
assert_eq!(writer.into_inner(), b"name,age\njane,42\n\"john,jr\",23\n");
let ser = Serializer::with_headers(SerializerConfig::new(), ["age", "name"]);
let mut writer = Writer::new(Vec::new(), ser);
writer.write(&row("jane", 42)).unwrap();
assert_eq!(writer.into_inner(), b"42,jane\n");
}
#[test]
fn test_single_values() {
let rows = vec![row("jane", 42), row("john", 23)];
let csv = SerializerConfig::new()
.to_string(&rows)
.unwrap()
.into_bytes();
assert_eq!(csv, b"name,age\njane,42\njohn,23\n");
let mut out = Vec::new();
deser_csv::to_writer(&mut out, &rows).unwrap();
assert_eq!(out, csv);
let back: Vec<Row> = DeserializerConfig::new().from_slice(&csv).unwrap();
assert_eq!(back, rows);
let back: Vec<Row> = DeserializerConfig::new().from_reader(&csv[..]).unwrap();
assert_eq!(back, rows);
let back: Vec<Row> = deser_csv::from_reader(&csv[..]).unwrap();
assert_eq!(back, rows);
}
#[test]
fn test_roundtrip_stream() {
let rows: Vec<Row> = (0..100)
.map(|index| row(&format!("name \"{}\"\n,", index), index))
.collect();
let mut writer = SerializerConfig::new().writer(Vec::new());
for row in &rows {
writer.write(row).unwrap();
}
let csv = writer.into_inner();
let mut reader = DeserializerConfig::new().reader(Chunked {
input: &csv,
size: 7,
});
let back = reader.iter::<Row>().collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(back, rows);
}
#[test]
fn test_to_writer_streams_records() {
use std::collections::BTreeMap;
struct Pieces(Vec<u8>, usize);
impl std::io::Write for Pieces {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.extend_from_slice(buf);
self.1 += 1;
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[derive(deser::Serialize)]
struct Row {
name: String,
age: u32,
note: Option<&'static str>,
}
let miri = cfg!(miri);
let rows: Vec<Row> = (0..if miri { 700 } else { 5000 })
.map(|idx| Row {
name: format!("person {idx}"),
age: idx % 100,
note: (idx % 3 == 0).then_some("with, comma"),
})
.collect();
let mut out = Pieces(Vec::new(), 0);
deser_csv::to_writer(&mut out, &rows).unwrap();
assert_eq!(out.0, deser_csv::to_string(&rows).unwrap().as_bytes());
assert!(out.1 > if miri { 1 } else { 5 }, "{}", out.1);
let maps: Vec<BTreeMap<String, u32>> = (0..if miri { 300 } else { 3000 })
.map(|idx| {
let mut map = BTreeMap::from([("b".to_string(), idx), ("a".to_string(), idx * 2)]);
if idx % 2 == 0 {
map.insert("c".into(), 1);
}
map
})
.collect();
const FLEXIBLE: SerializerConfig = SerializerConfig::builder().flexible(true).build();
let expected = FLEXIBLE.to_string(&maps);
let mut out = Pieces(Vec::new(), 0);
let rv = FLEXIBLE.to_writer(&mut out, &maps);
match expected {
Ok(expected) => {
rv.unwrap();
assert_eq!(out.0, expected.as_bytes());
}
Err(err) => assert_eq!(rv.unwrap_err().message(), err.message()),
}
let maps: Vec<BTreeMap<String, u32>> = (0..if miri { 1200 } else { 3000 })
.map(|idx| BTreeMap::from([("b".to_string(), idx), ("a".to_string(), idx * 2)]))
.collect();
let mut out = Pieces(Vec::new(), 0);
deser_csv::to_writer(&mut out, &maps).unwrap();
assert_eq!(out.0, deser_csv::to_string(&maps).unwrap().as_bytes());
assert!(out.1 > 1, "{}", out.1);
}