#[macro_use]
extern crate dbase;
use std::io::{Cursor, Read, Seek, Write};
use dbase::{
Date, DateTime, FieldIOError, FieldIterator, FieldName, FieldValue, FieldWriter,
ReadableRecord, Reader, Record, TableWriterBuilder, Time, WritableRecord,
};
use std::convert::{TryFrom, TryInto};
use std::fmt::Debug;
const LINE_DBF: &str = "./tests/data/line.dbf";
const NONE_FLOAT_DBF: &str = "./tests/data/contain_none_float.dbf";
const NULL_PADDED_NUMERIC_DBF: &str = "./tests/data/contain_null_padded_numeric.dbf";
const STATIONS: &str = "./tests/data/stations.dbf";
const STATIONS_WITH_DELETED: &str = "./tests/data/stations_with_deleted.dbf";
#[cfg(feature = "yore")]
const CP850_DBF: &str = "tests/data/cp850.dbf";
#[cfg(feature = "encoding_rs")]
const CP936_DBF: &str = "tests/data/cp936.dbf";
fn write_read_compare<R>(records: &Vec<R>, writer_builder: TableWriterBuilder)
where
R: WritableRecord + ReadableRecord + Debug + PartialEq,
{
let mut dst = Cursor::new(Vec::<u8>::new());
let writer = writer_builder.build_with_dest(&mut dst);
writer.write_records(records).unwrap();
dst.set_position(0);
let mut reader = Reader::new(dst).unwrap();
let read_records = reader.read_as::<R>().unwrap();
assert_eq!(&read_records, records);
}
#[test]
fn test_none_float() {
let records = dbase::read(NONE_FLOAT_DBF).unwrap();
assert_eq!(records.len(), 1);
let mut expected_fields = Record::default();
expected_fields.insert(
"name".to_owned(),
FieldValue::Character(Some("tralala".to_owned())),
);
expected_fields.insert("value_f".to_owned(), FieldValue::Float(Some(12.345)));
expected_fields.insert("value_f_non".to_owned(), FieldValue::Float(None));
expected_fields.insert("value_n".to_owned(), FieldValue::Numeric(Some(4.0)));
expected_fields.insert("value_n_non".to_owned(), FieldValue::Numeric(None));
assert_eq!(records[0], expected_fields);
}
#[test]
fn test_simple_file() {
let records = dbase::read(LINE_DBF).unwrap();
assert_eq!(records.len(), 1);
let mut expected_fields = Record::default();
expected_fields.insert(
"name".to_owned(),
FieldValue::Character(Some("linestring1".to_owned())),
);
assert_eq!(records[0], expected_fields);
}
#[test]
fn test_read_write_simple_file() {
let mut expected_fields = Record::default();
expected_fields.insert(
"name".to_owned(),
FieldValue::Character(Some("linestring1".to_owned())),
);
let mut reader = dbase::Reader::from_path(LINE_DBF).unwrap();
let records = reader.read().unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0], expected_fields);
let mut dst = Cursor::new(Vec::<u8>::new());
let writer = TableWriterBuilder::from_reader(reader).build_with_dest(&mut dst);
writer.write_records(&records).unwrap();
dst.set_position(0);
let mut reader = dbase::Reader::from_path(LINE_DBF).unwrap();
let records = reader.read().unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0], expected_fields);
}
#[test]
fn test_read_numeric_value_null_padded() {
let records = dbase::read(NULL_PADDED_NUMERIC_DBF).unwrap();
assert_eq!(records.len(), 1);
let mut expected_fields = Record::default();
expected_fields.insert("number".to_owned(), FieldValue::Numeric(Some(1234.)));
assert_eq!(records[0], expected_fields);
}
#[derive(Debug, PartialEq, Clone)]
struct Album {
artist: String,
name: String,
released: Date,
playtime: f64, available: bool,
}
impl ReadableRecord for Album {
fn read_using<R1, R2>(field_iterator: &mut FieldIterator<R1, R2>) -> Result<Self, FieldIOError>
where
R1: Read + Seek,
R2: Read + Seek,
{
Ok(Self {
artist: field_iterator.read_next_field_as()?.value,
name: field_iterator.read_next_field_as()?.value,
released: field_iterator.read_next_field_as()?.value,
playtime: field_iterator.read_next_field_as()?.value,
available: field_iterator.read_next_field_as()?.value,
})
}
}
impl WritableRecord for Album {
fn write_using<W: Write>(
&self,
field_writer: &mut FieldWriter<'_, W>,
) -> Result<(), FieldIOError> {
field_writer.write_next_field_value(&self.artist)?;
field_writer.write_next_field_value(&self.name)?;
field_writer.write_next_field_value(&self.released)?;
field_writer.write_next_field_value(&self.playtime)?;
field_writer.write_next_field_value(&self.available)?;
Ok(())
}
}
#[test]
fn from_scratch_dbase() {
let writer_builder = TableWriterBuilder::new()
.add_character_field("Artist".try_into().unwrap(), 50)
.add_character_field("Name".try_into().unwrap(), 50)
.add_date_field("Released".try_into().unwrap())
.add_numeric_field("Playtime".try_into().unwrap(), 10, 2)
.add_logical_field(FieldName::try_from("Available").unwrap());
let records = vec![
Album {
artist: "Fallujah".to_string(),
name: "The Flesh Prevails".to_string(),
released: Date::new(22, 6, 2014),
playtime: 2481.12f64,
available: false,
},
Album {
artist: "Beyond Creation".to_string(),
name: "Earthborn Evolution".to_string(),
released: Date::new(24, 10, 2014),
playtime: 24f64,
available: true,
},
];
write_read_compare(&records, writer_builder);
}
#[test]
fn from_scratch_fox_pro_record() {
let writer_builder = TableWriterBuilder::new()
.add_integer_field(FieldName::try_from("integer").unwrap())
.add_double_field(FieldName::try_from("double").unwrap())
.add_currency_field(FieldName::try_from("currency").unwrap())
.add_datetime_field(FieldName::try_from("datetime").unwrap());
let mut record = Record::default();
record.insert(String::from("integer"), FieldValue::Integer(17));
record.insert(String::from("double"), FieldValue::Double(54621.154));
record.insert(String::from("currency"), FieldValue::Currency(4567.134));
record.insert(
String::from("datetime"),
FieldValue::DateTime(DateTime::new(Date::new(1, 6, 2006), Time::new(12, 50, 20))),
);
let records = vec![record];
write_read_compare(&records, writer_builder);
}
dbase_record! {
#[derive(Clone, Debug, PartialEq)]
struct FoxProRecord {
datetime: DateTime,
double: f64,
currency: f64,
integer: i32
}
}
#[test]
fn from_scratch_fox_pro_struct_record() {
let writer_builder = TableWriterBuilder::new()
.add_datetime_field(FieldName::try_from("datetime").unwrap())
.add_double_field(FieldName::try_from("double").unwrap())
.add_currency_field(FieldName::try_from("currency").unwrap())
.add_integer_field(FieldName::try_from("integer").unwrap());
let records = vec![FoxProRecord {
datetime: DateTime::new(Date::new(12, 2, 1999), Time::new(21, 20, 35)),
double: 8649.48851,
currency: 3489.9612314,
integer: 42069,
}];
write_read_compare(&records, writer_builder);
}
dbase_record! {
#[derive(Clone, Debug, PartialEq)]
struct User {
first_name: String,
last_name: String,
}
}
dbase_record! {
struct TestStructWithoutDerive {
this_should_compile: String
}
}
#[test]
fn the_classical_user_record_example() {
let users = vec![
User {
first_name: "Ferrys".to_string(),
last_name: "Rust".to_string(),
},
User {
first_name: "Alex".to_string(),
last_name: "Rider".to_string(),
},
User {
first_name: "Jamie".to_string(),
last_name: "Oliver".to_string(),
},
];
let mut cursor = Cursor::new(Vec::<u8>::new());
let writer = TableWriterBuilder::new()
.add_character_field("First Name".try_into().unwrap(), 50)
.add_character_field("Last Name".try_into().unwrap(), 50)
.build_with_dest(&mut cursor);
writer.write_records(&users).unwrap();
cursor.set_position(0);
let mut reader = Reader::new(cursor).unwrap();
let read_records = reader.read_as::<User>().unwrap();
assert_eq!(read_records, users);
}
#[cfg(feature = "yore")]
#[test]
fn non_unicode_codepages() {
let mut reader =
dbase::Reader::from_path_with_encoding(CP850_DBF, yore::code_pages::CP850).unwrap();
let records = reader.read().unwrap();
assert_eq!(
records[0].get("TEXT"),
Some(&FieldValue::Character(Some("Äöü!§$%&/".to_string())))
);
let mut cursor = Cursor::new(Vec::<u8>::new());
{
let writer = TableWriterBuilder::from_reader(reader).build_with_dest(&mut cursor);
writer.write_records(&records).unwrap();
}
cursor.set_position(0);
let mut reader = Reader::new_with_encoding(cursor, yore::code_pages::CP850).unwrap();
let records = reader.read().unwrap();
assert_eq!(
records[0].get("TEXT"),
Some(&FieldValue::Character(Some("Äöü!§$%&/".to_string())))
);
}
dbase::dbase_record!(
#[derive(PartialOrd, PartialEq, Debug)]
struct StationRecord {
name: String,
marker_col: String,
marker_sym: String,
line: String,
}
);
#[test]
fn test_char_trimming() -> Result<(), Box<dyn std::error::Error>> {
let mut reader = dbase::Reader::from_path(STATIONS)?;
let options = dbase::ReadingOptions::default().character_trim(dbase::TrimOption::Begin);
reader.set_options(options);
let records = reader.read_as::<StationRecord>()?;
assert_eq!(records.len() as u32, reader.header().num_records);
let expected = StationRecord {
name: format!(
"{:width$}",
"Franconia-Springfield",
width = reader.fields()[0].length() as usize
),
marker_col: format!(
"{:width$}",
"#0000ff",
width = reader.fields()[0].length() as usize
),
marker_sym: format!(
"{:width$}",
"rail-metro",
width = reader.fields()[0].length() as usize
),
line: format!(
"{:width$}",
"blue",
width = reader.fields()[0].length() as usize
),
};
assert_eq!(records[1], expected);
Ok(())
}
#[test]
fn test_record_marked_as_deleted_are_skipped_by_reader() -> Result<(), Box<dyn std::error::Error>> {
let mut reader = dbase::Reader::from_path(STATIONS_WITH_DELETED)?;
let records = reader.read_as::<StationRecord>()?;
assert_eq!(records.len() as u32, reader.header().num_records - 1);
let expected = StationRecord {
name: "Franconia-Springfield".to_string(),
marker_col: "#0000ff".to_string(),
marker_sym: "rail-metro".to_string(),
line: "blue".to_string(),
};
assert_eq!(records[0], expected);
Ok(())
}
#[cfg(feature = "encoding_rs")]
#[test]
fn test_codepages_cp936() {
let mut reader = dbase::Reader::from_path(CP936_DBF).unwrap();
let records = reader.read().unwrap();
assert_eq!(
records[0].get("TEST"),
Some(&FieldValue::Character(Some("测试中文".to_string())))
);
}