use std::fs;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use mrrc::{MarcReader, RecoveryMode, ValidationLevel};
struct Cell {
validation: ValidationLevel,
recovery: RecoveryMode,
expected_error: Option<&'static str>,
}
fn fixture_bytes(name: &str) -> Vec<u8> {
let path: PathBuf = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/data/error_fixtures")
.join(name);
fs::read(&path).unwrap_or_else(|e| panic!("read fixture {}: {e}", path.display()))
}
fn drain(
bytes: &[u8],
validation: ValidationLevel,
recovery: RecoveryMode,
) -> Result<usize, String> {
let mut reader = MarcReader::new(Cursor::new(bytes.to_vec()))
.with_recovery_mode(recovery)
.with_validation_level(validation);
let mut count = 0usize;
loop {
match reader.read_record() {
Ok(Some(_)) => count += 1,
Ok(None) => return Ok(count),
Err(e) => return Err(e.code().to_string()),
}
}
}
fn run_strict_only_matrix(fixture_name: &str, error_code: &'static str) {
let bytes = fixture_bytes(fixture_name);
for validation in [ValidationLevel::Structural, ValidationLevel::StrictMarc] {
for (recovery, expect_fire) in [
(RecoveryMode::Strict, true),
(RecoveryMode::Lenient, false),
(RecoveryMode::Permissive, false),
] {
let outcome = drain(&bytes, validation, recovery);
match (expect_fire, outcome) {
(true, Err(actual)) => assert_eq!(
actual, error_code,
"fixture {fixture_name} at ({validation:?}, {recovery:?}): expected error {error_code}, got {actual}",
),
(true, Ok(n)) => panic!(
"fixture {fixture_name} at ({validation:?}, {recovery:?}): expected error {error_code}, got clean iteration ({n} records)",
),
(false, Ok(_)) => {},
(false, Err(actual)) => panic!(
"fixture {fixture_name} at ({validation:?}, {recovery:?}): expected clean iteration (recovery cap absorbs), got error {actual}",
),
}
}
}
}
fn run_fatal_matrix(fixture_name: &str, error_code: &'static str) {
let bytes = fixture_bytes(fixture_name);
for validation in [ValidationLevel::Structural, ValidationLevel::StrictMarc] {
for recovery in [
RecoveryMode::Strict,
RecoveryMode::Lenient,
RecoveryMode::Permissive,
] {
match drain(&bytes, validation, recovery) {
Err(actual) => assert_eq!(
actual, error_code,
"fixture {fixture_name} at ({validation:?}, {recovery:?}): expected fatal error {error_code}, got {actual}",
),
Ok(n) => panic!(
"fixture {fixture_name} at ({validation:?}, {recovery:?}): expected fatal error {error_code}, got clean iteration ({n} records)",
),
}
}
}
}
fn run_matrix(fixture_name: &str, strict_error_code: &'static str) {
let bytes = fixture_bytes(fixture_name);
let cells = [
Cell {
validation: ValidationLevel::Structural,
recovery: RecoveryMode::Strict,
expected_error: None,
},
Cell {
validation: ValidationLevel::Structural,
recovery: RecoveryMode::Lenient,
expected_error: None,
},
Cell {
validation: ValidationLevel::Structural,
recovery: RecoveryMode::Permissive,
expected_error: None,
},
Cell {
validation: ValidationLevel::StrictMarc,
recovery: RecoveryMode::Strict,
expected_error: Some(strict_error_code),
},
Cell {
validation: ValidationLevel::StrictMarc,
recovery: RecoveryMode::Lenient,
expected_error: None,
},
Cell {
validation: ValidationLevel::StrictMarc,
recovery: RecoveryMode::Permissive,
expected_error: None,
},
];
for cell in &cells {
let outcome = drain(&bytes, cell.validation, cell.recovery);
match (cell.expected_error, outcome) {
(None, Ok(_)) => {},
(Some(code), Err(actual)) => assert_eq!(
actual, code,
"fixture {fixture_name} at ({:?}, {:?}): expected error code {code}, got {actual}",
cell.validation, cell.recovery
),
(None, Err(actual)) => panic!(
"fixture {fixture_name} at ({:?}, {:?}): expected clean iteration, got error {actual}",
cell.validation, cell.recovery
),
(Some(code), Ok(n)) => panic!(
"fixture {fixture_name} at ({:?}, {:?}): expected error {code}, got clean iteration ({n} records)",
cell.validation, cell.recovery
),
}
}
}
#[test]
fn matrix_e201_bad_indicator() {
run_matrix("e201_bad_indicator.bin", "E201");
}
#[test]
fn matrix_e202_bad_subfield_code() {
run_matrix("e202_non_printable_subfield_code.bin", "E202");
}
#[test]
fn matrix_e301_invalid_utf8() {
run_matrix("e301_invalid_utf8_in_subfield.bin", "E301");
}
#[test]
fn matrix_e201_per_tag_indicator_245() {
run_matrix("e201_per_tag_indicator_245.bin", "E201");
}
#[test]
fn matrix_e002_invalid_record_status() {
run_matrix("e002_invalid_record_status.bin", "E002");
}
#[test]
fn fatal_e001_record_length_non_digit() {
run_fatal_matrix("e001_record_length_non_digit.bin", "E001");
}
#[test]
fn fatal_e002_indicator_count_non_digit() {
run_fatal_matrix("e002_indicator_count_non_digit.bin", "E002");
}
#[test]
fn fatal_e003_base_address_non_digit() {
run_fatal_matrix("e003_base_address_non_digit.bin", "E003");
}
#[test]
fn fatal_e004_base_address_past_record() {
run_fatal_matrix("e004_base_address_past_record.bin", "E004");
}
#[test]
fn strict_only_e006_no_record_terminator() {
run_strict_only_matrix("e006_no_record_terminator.bin", "E006");
}