#![doc = include_str!("../../docs/matrix/io.md")]
use std::fs::File;
use std::io::prelude::*;
use std::io::{BufRead, BufReader};
use std::path::Path;
use thiserror::Error;
use super::items::Coordinates;
use super::iter::OnesCoordinatesColumnCursor;
use super::CSVBinaryMatrix;
#[derive(Error, Debug)]
pub enum ParseHeaderError {
#[error("There is no header")]
NoHeaderError,
#[error("At least one field is missing in the header")]
MissingFieldError,
#[error("Invalid number of rows: {0} (expected a strictly positive integer)")]
ParseNumberRowsError(String),
#[error("Invalid number of columns: {0} (expected a strictly positive integer)")]
ParseNumberColumnsError(String),
#[error("Invalid number of ones: {0} (expected an integer)")]
ParseNumberOnesError(String),
#[error("Invalid is_reversed flag: {0} (expected 0 or 1)")]
ParseIsReversedError(String),
}
#[derive(Error, Debug)]
pub enum ParseDistancesError {
#[error("Invalid distance: {0} (expected an integer)")]
ParseDistanceError(String),
#[error("Invalid number of ones: too many distances ({0} > {1})")]
TooManyDistancesError(usize, usize),
#[error("Invalid number of ones: too few distances ({0} < {1})")]
TooFewDistancesError(usize, usize),
#[error("Coordinates exceed the number of rows: {0} >= {1}")]
CoordinatesExceedRowsError(usize, usize),
#[error("Failed to read a line")]
ReadLineError(#[from] std::io::Error),
}
#[derive(Error, Debug)]
pub enum ParseCSVBMFileError {
#[error("The header is invalid: {0}")]
ParseHeaderError(#[from] ParseHeaderError),
#[error("The list of distances is invalid: {0}")]
ParseDistancesError(#[from] ParseDistancesError),
#[error("Opening the file failed: {0}")]
OpenFileError(#[from] std::io::Error),
}
fn parse_header(
iter_line: &mut std::io::Lines<BufReader<File>>,
) -> Result<(usize, usize, usize, bool), ParseHeaderError> {
let binding = match iter_line.next() {
Some(Ok(x)) => x,
_ => return Err(ParseHeaderError::NoHeaderError),
};
let header: Vec<&str> = binding.split_whitespace().collect();
if header.len() < 4 {
return Err(ParseHeaderError::MissingFieldError);
}
let number_of_rows: usize = unsafe {
match header.get_unchecked(0).parse::<usize>() {
Ok(x) => {
if x == 0 {
return Err(ParseHeaderError::ParseNumberRowsError(
(*header.get_unchecked(0)).to_string(),
));
}
x
}
Err(_) => {
return Err(ParseHeaderError::ParseNumberRowsError(
(*header.get_unchecked(0)).to_string(),
))
}
}
};
let number_of_columns: usize = unsafe {
match header.get_unchecked(1).parse::<usize>() {
Ok(x) => {
if x == 0 {
return Err(ParseHeaderError::ParseNumberColumnsError(
(*header.get_unchecked(1)).to_string(),
));
}
x
}
Err(_) => {
return Err(ParseHeaderError::ParseNumberColumnsError(
(*header.get_unchecked(1)).to_string(),
))
}
}
};
let number_of_ones: usize = unsafe {
match header.get_unchecked(2).parse::<usize>() {
Ok(x) => x,
Err(_) => {
return Err(ParseHeaderError::ParseNumberOnesError(
(*header.get_unchecked(2)).to_string(),
))
}
}
};
let is_reversed: bool = unsafe {
match header.get_unchecked(3).parse::<u8>() {
Ok(0) => false,
Ok(1) => true,
_ => {
return Err(ParseHeaderError::ParseIsReversedError(
(*header.get_unchecked(3)).to_string(),
))
}
}
};
Ok((
number_of_rows,
number_of_columns,
number_of_ones,
is_reversed,
))
}
fn from_file_inorder(
iter_line: std::io::Lines<BufReader<File>>,
number_of_rows: usize,
number_of_columns: usize,
number_of_ones: usize,
) -> Result<Vec<usize>, ParseDistancesError> {
let mut distances = Vec::with_capacity(number_of_ones + 1);
let mut coordinates = Coordinates::new(0, 0);
let mut ones_coordinates_column_cursor =
OnesCoordinatesColumnCursor::new(number_of_columns, coordinates.clone());
let mut distance: usize;
for (count_ones, line) in iter_line.enumerate() {
if count_ones >= number_of_ones {
return Err(ParseDistancesError::TooManyDistancesError(
count_ones + 1,
number_of_ones,
));
}
distance = match line {
Ok(x) => match x.parse() {
Ok(x) => x,
Err(_) => return Err(ParseDistancesError::ParseDistanceError(x.to_string())),
},
Err(_) => {
return Err(ParseDistancesError::ReadLineError(line.unwrap_err()));
}
};
coordinates = ones_coordinates_column_cursor.forward(distance);
if coordinates.row() >= number_of_rows {
return Err(ParseDistancesError::CoordinatesExceedRowsError(
coordinates.row(),
number_of_rows,
));
}
distances.push(distance);
}
distances.push(
(number_of_rows - 1 - coordinates.row()) * number_of_columns
+ (number_of_columns - 1 - coordinates.column()),
);
if distances.len() < number_of_ones + 1 {
return Err(ParseDistancesError::TooFewDistancesError(
distances.len() - 1,
number_of_ones,
));
}
Ok(distances)
}
fn from_file_reverse(
iter_line: std::io::Lines<BufReader<File>>,
number_of_rows: usize,
number_of_columns: usize,
number_of_ones: usize,
) -> Result<Vec<usize>, ParseDistancesError> {
let mut distances = vec![0; number_of_ones + 1];
let mut distance_index = number_of_ones;
let mut coordinates = Coordinates::new(number_of_rows - 1, number_of_columns - 1);
let mut ones_coordinates_column_cursor =
OnesCoordinatesColumnCursor::new(number_of_columns, coordinates.clone());
let mut distance: usize;
for (count_ones, line) in iter_line.enumerate() {
if count_ones >= number_of_ones {
return Err(ParseDistancesError::TooManyDistancesError(
number_of_ones + 1,
number_of_ones,
));
}
distance = match line {
Ok(x) => match x.parse() {
Ok(x) => x,
Err(_) => return Err(ParseDistancesError::ParseDistanceError(x.to_string())),
},
Err(_) => {
return Err(ParseDistancesError::ReadLineError(line.unwrap_err()));
}
};
coordinates = match ones_coordinates_column_cursor.try_backward(distance) {
Ok(coord) => coord,
Err(e) => {
return Err(ParseDistancesError::CoordinatesExceedRowsError(
number_of_rows + e.surplus_rows - 1,
number_of_rows,
));
}
};
unsafe { *distances.get_unchecked_mut(distance_index) = distance }
distance_index -= 1;
}
unsafe {
*distances.get_unchecked_mut(0) =
coordinates.row() * number_of_columns + coordinates.column();
}
if distance_index > 0 {
return Err(ParseDistancesError::TooFewDistancesError(
number_of_ones - distance_index,
number_of_ones,
));
}
Ok(distances)
}
impl CSVBinaryMatrix {
pub fn to_file<P: AsRef<Path>>(&self, filepath: P) -> Result<(), std::io::Error> {
let mut file = File::create(filepath.as_ref())?;
file.write_all(
format!(
"{} {} {} {}",
self.number_of_rows,
self.number_of_columns,
self.number_of_ones(),
u8::from(self.is_reversed),
)
.as_bytes(),
)?;
file.write_all(b"\n")?;
for distance in &self.distances[..self.distances.len() - 1] {
file.write_all(format!("{distance}").as_bytes())?;
file.write_all(b"\n")?;
}
file.flush()?;
Ok(())
}
pub fn into_file<P: AsRef<Path>>(mut self, filepath: P) -> Result<(), std::io::Error> {
let mut file = File::create(filepath.as_ref())?;
file.write_all(
format!(
"{} {} {} {}",
self.number_of_rows,
self.number_of_columns,
self.number_of_ones(),
u8::from(!self.is_reversed),
)
.as_bytes(),
)?;
file.write_all(b"\n")?;
let mut distance_index = self.distances.len() - 1;
while distance_index > 0 {
let distance = self.distances.pop().unwrap();
file.write_all(format!("{distance}").as_bytes())?;
file.write_all(b"\n")?;
distance_index -= 1;
}
file.flush()?;
Ok(())
}
pub fn try_from_file<P: AsRef<Path>>(filename: P) -> Result<Self, ParseCSVBMFileError> {
let file = File::open(filename.as_ref()).map_err(ParseCSVBMFileError::OpenFileError)?;
let rdr = BufReader::new(file);
let mut iter_line = rdr.lines();
let (number_of_rows, number_of_columns, number_of_ones, is_reversed) =
{ parse_header(&mut iter_line)? };
let distances: Vec<usize> = if is_reversed {
from_file_reverse(iter_line, number_of_rows, number_of_columns, number_of_ones)?
} else {
from_file_inorder(iter_line, number_of_rows, number_of_columns, number_of_ones)?
};
Ok(Self {
number_of_rows,
number_of_columns,
distances,
is_reversed: false,
})
}
}
#[cfg(test)]
mod tests {
mod ok {
use crate::matrix::tests::matrix_a_bis;
use super::super::super::tests::{matrix_a, ones_matrix, zeros_matrix};
use super::super::super::CSVBinaryMatrix;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::{fixture, rstest};
use std::env;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
#[fixture]
fn ok_csvbm_dir() -> PathBuf {
PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("src/matrix/tests/io/ok")
}
#[fixture]
fn matrix_a_filepath() -> PathBuf {
ok_csvbm_dir().join("matrix_a.csvbm")
}
#[fixture]
fn matrix_a_rev_filepath() -> PathBuf {
ok_csvbm_dir().join("matrix_a_rev.csvbm")
}
#[fixture]
fn zeros_matrix_filepath() -> PathBuf {
ok_csvbm_dir().join("zeros_matrix.csvbm")
}
#[fixture]
fn zeros_matrix_rev_filepath() -> PathBuf {
ok_csvbm_dir().join("zeros_matrix_rev.csvbm")
}
#[fixture]
fn ones_matrix_filepath() -> PathBuf {
ok_csvbm_dir().join("ones_matrix.csvbm")
}
#[fixture]
fn ones_matrix_rev_filepath() -> PathBuf {
ok_csvbm_dir().join("ones_matrix_rev.csvbm")
}
fn assert_file_contents_equal<P: AsRef<Path>>(to_test_file: P, expected_file: P) {
let mut file = std::fs::File::open(to_test_file).unwrap();
let mut file_contents = String::new();
file.read_to_string(&mut file_contents).unwrap();
let mut file = std::fs::File::open(expected_file).unwrap();
let mut file_contents_expected = String::new();
file.read_to_string(&mut file_contents_expected).unwrap();
assert_str_eq!(file_contents, file_contents_expected);
}
#[rstest]
#[case(matrix_a(), matrix_a_filepath())]
#[case(matrix_a_bis(), matrix_a_rev_filepath())]
#[case(zeros_matrix(), zeros_matrix_filepath())]
#[case(ones_matrix(), ones_matrix_filepath())]
fn to_file(#[case] matrix: CSVBinaryMatrix, #[case] expected_filepath: PathBuf) {
let tmp_file = tempfile::NamedTempFile::new().unwrap();
matrix.to_file(tmp_file.path()).unwrap();
assert_file_contents_equal(tmp_file.path(), &expected_filepath);
std::fs::remove_file(tmp_file).unwrap();
}
#[rstest]
#[case(matrix_a(), matrix_a_rev_filepath())]
#[case(matrix_a_bis(), matrix_a_filepath())]
#[case(zeros_matrix(), zeros_matrix_rev_filepath())]
#[case(ones_matrix(), ones_matrix_rev_filepath())]
fn into_file(#[case] matrix: CSVBinaryMatrix, #[case] expected_filepath: PathBuf) {
let tmp_file = tempfile::NamedTempFile::new().unwrap();
matrix.into_file(tmp_file.path()).unwrap();
assert_file_contents_equal(tmp_file.path(), &expected_filepath);
std::fs::remove_file(tmp_file.path()).unwrap();
}
#[rstest]
#[case(matrix_a_filepath(), matrix_a())]
#[case(zeros_matrix_filepath(), zeros_matrix())]
#[case(ones_matrix_filepath(), ones_matrix())]
#[case(matrix_a_rev_filepath(), matrix_a())]
#[case(zeros_matrix_rev_filepath(), zeros_matrix())]
#[case(ones_matrix_rev_filepath(), ones_matrix())]
fn try_from_file(#[case] test_filepath: PathBuf, #[case] expected_matrix: CSVBinaryMatrix) {
assert_eq!(
CSVBinaryMatrix::try_from_file(test_filepath).unwrap(),
expected_matrix
);
}
}
mod parse_header_errors {
use super::super::super::CSVBinaryMatrix;
use super::super::{ParseCSVBMFileError, ParseHeaderError};
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::{fixture, rstest};
use std::env;
use std::error::Error;
use std::path::PathBuf;
#[fixture]
fn invalid_csvbm_dir() -> PathBuf {
PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
.join("src/matrix/tests/io/parse_header_errors")
}
#[fixture]
fn no_header_filepath() -> PathBuf {
invalid_csvbm_dir().join("no_header.csvbm")
}
#[fixture]
fn invalid_missing_field_filepath() -> PathBuf {
invalid_csvbm_dir().join("invalid_missing_field.csvbm")
}
#[fixture]
fn invalid_number_of_rows_filepath() -> PathBuf {
invalid_csvbm_dir().join("invalid_number_of_rows.csvbm")
}
#[fixture]
fn invalid_null_rows_filepath() -> PathBuf {
invalid_csvbm_dir().join("invalid_null_rows.csvbm")
}
#[fixture]
fn invalid_number_of_columns_filepath() -> PathBuf {
invalid_csvbm_dir().join("invalid_number_of_columns.csvbm")
}
#[fixture]
fn invalid_null_columns_filepath() -> PathBuf {
invalid_csvbm_dir().join("invalid_null_columns.csvbm")
}
#[fixture]
fn invalid_number_of_ones_filepath() -> PathBuf {
invalid_csvbm_dir().join("invalid_number_of_ones.csvbm")
}
#[fixture]
fn invalid_is_reversed_filepath() -> PathBuf {
invalid_csvbm_dir().join("invalid_is_reversed.csvbm")
}
#[rstest]
#[case(ParseHeaderError::MissingFieldError, "None")]
#[case(ParseHeaderError::MissingFieldError, "None")]
#[case(ParseHeaderError::ParseNumberRowsError("a".to_string()), "None")]
#[case(ParseHeaderError::ParseNumberColumnsError("b".to_string()), "None")]
#[case(ParseHeaderError::ParseNumberOnesError("c".to_string()), "None")]
#[case(ParseHeaderError::ParseIsReversedError("2".to_string()), "None")]
fn error_derive(#[case] error: ParseHeaderError, #[case] expected_source_debug: String) {
assert_str_eq!(format!("{:?}", error.source()), expected_source_debug);
}
#[rstest]
fn debug() {
assert_str_eq!(
format!("{:?}", ParseHeaderError::MissingFieldError),
"MissingFieldError"
);
assert_str_eq!(
format!(
"{:?}",
ParseHeaderError::ParseNumberRowsError("a".to_string())
),
"ParseNumberRowsError(\"a\")"
);
assert_str_eq!(
format!(
"{:?}",
ParseHeaderError::ParseNumberColumnsError("a".to_string())
),
"ParseNumberColumnsError(\"a\")"
);
assert_str_eq!(
format!(
"{:?}",
ParseHeaderError::ParseNumberOnesError("a".to_string())
),
"ParseNumberOnesError(\"a\")"
);
}
#[rstest]
fn from_file_no_header_error(no_header_filepath: PathBuf) {
match CSVBinaryMatrix::try_from_file(no_header_filepath) {
Ok(_) => panic!("Expected error"),
Err(e) => {
assert!(matches!(
e,
ParseCSVBMFileError::ParseHeaderError(ParseHeaderError::NoHeaderError)
));
assert_eq!(format!("{e:?}"), "ParseHeaderError(NoHeaderError)");
assert_eq!(format!("{e}"), "The header is invalid: There is no header");
}
}
}
#[rstest]
fn from_file_invalid_missing_field(invalid_missing_field_filepath: PathBuf) {
match CSVBinaryMatrix::try_from_file(invalid_missing_field_filepath) {
Ok(_) => panic!("Expected error"),
Err(ParseCSVBMFileError::ParseHeaderError(e)) => {
assert!(matches!(e, ParseHeaderError::MissingFieldError));
assert_eq!(
format!("{e}"),
"At least one field is missing in the header"
);
}
Err(e) => panic!("Unexpected error: {e:?}"),
}
}
#[rstest]
fn from_file_invalid_null_rows(invalid_null_rows_filepath: PathBuf) {
match CSVBinaryMatrix::try_from_file(invalid_null_rows_filepath) {
Ok(_) => panic!("Expected error"),
Err(ParseCSVBMFileError::ParseHeaderError(e)) => {
assert!(matches!(e, ParseHeaderError::ParseNumberRowsError(_)));
assert_eq!(
format!("{e}"),
"Invalid number of rows: 0 (expected a strictly positive integer)"
);
}
Err(e) => panic!("Unexpected error: {e:?}"),
}
}
#[rstest]
fn from_file_invalid_number_of_rows(invalid_number_of_rows_filepath: PathBuf) {
match CSVBinaryMatrix::try_from_file(invalid_number_of_rows_filepath) {
Ok(_) => panic!("Expected error"),
Err(ParseCSVBMFileError::ParseHeaderError(e)) => {
assert!(matches!(e, ParseHeaderError::ParseNumberRowsError(_)));
assert_eq!(
format!("{e}"),
"Invalid number of rows: InvalidRows (expected a strictly positive integer)"
);
}
Err(e) => panic!("Unexpected error: {e:?}"),
}
}
#[rstest]
fn from_file_invalid_null_columns(invalid_null_columns_filepath: PathBuf) {
match CSVBinaryMatrix::try_from_file(invalid_null_columns_filepath) {
Ok(_) => panic!("Expected error"),
Err(ParseCSVBMFileError::ParseHeaderError(e)) => {
assert!(matches!(e, ParseHeaderError::ParseNumberColumnsError(_)));
assert_eq!(
format!("{e}"),
"Invalid number of columns: 0 (expected a strictly positive integer)"
);
}
Err(e) => panic!("Unexpected error: {e:?}"),
}
}
#[rstest]
fn from_file_invalid_number_of_columns(invalid_number_of_columns_filepath: PathBuf) {
match CSVBinaryMatrix::try_from_file(invalid_number_of_columns_filepath) {
Ok(_) => panic!("Expected error"),
Err(ParseCSVBMFileError::ParseHeaderError(e)) => {
assert!(matches!(e, ParseHeaderError::ParseNumberColumnsError(_)));
assert_eq!(format!("{e}"), "Invalid number of columns: InvalidColumns (expected a strictly positive integer)");
}
Err(e) => panic!("Unexpected error: {e:?}"),
}
}
#[rstest]
fn from_file_invalid_number_of_ones(invalid_number_of_ones_filepath: PathBuf) {
match CSVBinaryMatrix::try_from_file(invalid_number_of_ones_filepath) {
Ok(_) => panic!("Expected error"),
Err(ParseCSVBMFileError::ParseHeaderError(e)) => {
assert!(matches!(e, ParseHeaderError::ParseNumberOnesError(_)));
assert_eq!(
format!("{e}"),
"Invalid number of ones: InvalidOnes (expected an integer)"
);
}
Err(e) => panic!("Unexpected error: {e:?}"),
}
}
#[rstest]
fn from_file_invalid_is_reversed(invalid_is_reversed_filepath: PathBuf) {
match CSVBinaryMatrix::try_from_file(invalid_is_reversed_filepath) {
Ok(_) => panic!("Expected error"),
Err(ParseCSVBMFileError::ParseHeaderError(e)) => {
assert!(matches!(e, ParseHeaderError::ParseIsReversedError(_)));
assert_eq!(
format!("{e}"),
"Invalid is_reversed flag: 2 (expected 0 or 1)"
);
}
Err(e) => panic!("Unexpected error: {e:?}"),
}
}
}
mod parse_distances_errors {
use super::super::super::CSVBinaryMatrix;
use super::super::{ParseCSVBMFileError, ParseDistancesError};
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::{fixture, rstest};
use std::env;
use std::error::Error;
use std::path::PathBuf;
#[fixture]
fn invalid_csvbm_dir() -> PathBuf {
PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap())
.join("src/matrix/tests/io/parse_distances_errors")
}
#[fixture]
fn distances_overflow_filepath() -> PathBuf {
invalid_csvbm_dir().join("distances_overflow.csvbm")
}
#[fixture]
fn distances_overflow_rev_filepath() -> PathBuf {
invalid_csvbm_dir().join("distances_overflow_rev.csvbm")
}
#[fixture]
fn invalid_distance_filepath() -> PathBuf {
invalid_csvbm_dir().join("invalid_distance.csvbm")
}
#[fixture]
fn invalid_distance_rev_filepath() -> PathBuf {
invalid_csvbm_dir().join("invalid_distance_rev.csvbm")
}
#[fixture]
fn too_few_ones_filepath() -> PathBuf {
invalid_csvbm_dir().join("too_few_ones.csvbm")
}
#[fixture]
fn too_few_ones_rev_filepath() -> PathBuf {
invalid_csvbm_dir().join("too_few_ones_rev.csvbm")
}
#[fixture]
fn too_many_ones_filepath() -> PathBuf {
invalid_csvbm_dir().join("too_many_ones.csvbm")
}
#[fixture]
fn too_many_ones_rev_filepath() -> PathBuf {
invalid_csvbm_dir().join("too_many_ones_rev.csvbm")
}
#[rstest]
#[case(ParseDistancesError::ParseDistanceError("a".to_string()), "None")]
#[case(ParseDistancesError::TooManyDistancesError(3, 3), "None")]
#[case(ParseDistancesError::TooFewDistancesError(3, 3), "None")]
#[case(ParseDistancesError::CoordinatesExceedRowsError(3, 3), "None")]
#[case(
ParseDistancesError::ReadLineError(std::io::Error::new(
std::io::ErrorKind::Other,
"a"
)),
"Some(Custom { kind: Other, error: \"a\" })"
)]
fn error_derive(#[case] error: ParseDistancesError, #[case] expected_source_debug: String) {
assert_str_eq!(format!("{:?}", error.source()), expected_source_debug);
}
#[rstest]
fn debug() {
assert_str_eq!(
format!(
"{:?}",
ParseDistancesError::ParseDistanceError("a".to_string())
),
"ParseDistanceError(\"a\")"
);
assert_str_eq!(
format!("{:?}", ParseDistancesError::TooManyDistancesError(3, 3)),
"TooManyDistancesError(3, 3)"
);
assert_str_eq!(
format!("{:?}", ParseDistancesError::TooFewDistancesError(3, 3)),
"TooFewDistancesError(3, 3)"
);
assert_str_eq!(
format!(
"{:?}",
ParseDistancesError::CoordinatesExceedRowsError(3, 3)
),
"CoordinatesExceedRowsError(3, 3)"
);
assert_str_eq!(
format!(
"{:?}",
ParseDistancesError::ReadLineError(std::io::Error::new(
std::io::ErrorKind::Other,
"Other"
))
),
"ReadLineError(Custom { kind: Other, error: \"Other\" })"
);
}
#[rstest]
#[case(distances_overflow_filepath())]
#[case(distances_overflow_rev_filepath())]
fn from_file_distances_overflow(#[case] invalid_filepath: PathBuf) {
match CSVBinaryMatrix::try_from_file(invalid_filepath) {
Ok(_) => panic!("Expected error"),
Err(ParseCSVBMFileError::ParseDistancesError(e)) => {
assert!(matches!(
e,
ParseDistancesError::CoordinatesExceedRowsError(_, _)
));
assert_eq!(
format!("{e}"),
"Coordinates exceed the number of rows: 3 >= 3"
);
}
Err(e) => panic!("Unexpected error: {e:?}"),
}
}
#[rstest]
#[case(invalid_distance_filepath())]
#[case(invalid_distance_rev_filepath())]
fn from_file_invalid_distance(#[case] invalid_filepath: PathBuf) {
match CSVBinaryMatrix::try_from_file(invalid_filepath) {
Ok(_) => panic!("Expected error"),
Err(e) => {
assert!(matches!(
e,
ParseCSVBMFileError::ParseDistancesError(
ParseDistancesError::ParseDistanceError(_)
)
));
assert_eq!(
format!("{e}"),
"The list of distances is invalid: Invalid distance: InvalidData (expected an integer)"
);
}
}
}
#[rstest]
#[case(too_few_ones_filepath())]
#[case(too_few_ones_rev_filepath())]
fn from_file_too_few_ones(#[case] invalid_filepath: PathBuf) {
match CSVBinaryMatrix::try_from_file(invalid_filepath) {
Ok(_) => panic!("Expected error"),
Err(ParseCSVBMFileError::ParseDistancesError(e)) => {
assert!(matches!(e, ParseDistancesError::TooFewDistancesError(_, _)));
assert_eq!(
format!("{e}"),
"Invalid number of ones: too few distances (4 < 5)"
);
}
Err(e) => panic!("Unexpected error: {e:?}"),
}
}
#[rstest]
#[case(too_many_ones_filepath())]
#[case(too_many_ones_rev_filepath())]
fn from_file_too_many_ones(#[case] invalid_filepath: PathBuf) {
match CSVBinaryMatrix::try_from_file(invalid_filepath) {
Ok(_) => panic!("Expected error"),
Err(ParseCSVBMFileError::ParseDistancesError(e)) => {
assert!(matches!(
e,
ParseDistancesError::TooManyDistancesError(_, _)
));
assert_eq!(
format!("{e}"),
"Invalid number of ones: too many distances (6 > 5)"
);
}
Err(e) => panic!("Unexpected error: {e:?}"),
}
}
}
mod parse_csvbm_file_error {
use super::super::{ParseCSVBMFileError, ParseDistancesError, ParseHeaderError};
use pretty_assertions::assert_str_eq;
use rstest::rstest;
use std::error::Error;
#[rstest]
fn error_derive() {
assert_str_eq!(
format!(
"{:?}",
ParseCSVBMFileError::ParseHeaderError(ParseHeaderError::MissingFieldError)
.source()
),
"Some(MissingFieldError)"
);
assert_str_eq!(
format!(
"{:?}",
ParseCSVBMFileError::ParseDistancesError(
ParseDistancesError::ParseDistanceError("InvalidData".to_string())
)
.source()
),
"Some(ParseDistanceError(\"InvalidData\"))"
);
assert_str_eq!(
format!(
"{:?}",
ParseCSVBMFileError::OpenFileError(std::io::Error::new(
std::io::ErrorKind::Other,
"Other"
))
.source()
),
"Some(Custom { kind: Other, error: \"Other\" })"
);
}
#[rstest]
fn debug() {
assert_str_eq!(
format!(
"{:?}",
ParseCSVBMFileError::ParseHeaderError(ParseHeaderError::MissingFieldError)
),
"ParseHeaderError(MissingFieldError)"
);
assert_str_eq!(
format!(
"{:?}",
ParseCSVBMFileError::ParseDistancesError(
ParseDistancesError::ParseDistanceError("InvalidData".to_string())
)
),
"ParseDistancesError(ParseDistanceError(\"InvalidData\"))"
);
assert_str_eq!(
format!(
"{:?}",
ParseCSVBMFileError::OpenFileError(std::io::Error::new(
std::io::ErrorKind::Other,
"Other"
))
),
"OpenFileError(Custom { kind: Other, error: \"Other\" })"
);
}
}
}