use crate::{EntryKind, Error, Fidelity, FidelityReport};
pub fn check_error_is_classified(e: &Error) -> Result<(), String> {
match e.exit_code() {
1 => Err(format!(
"a decoder raised {e:?}, which maps to exit code 1 — that means \
stuffr failed, not that the input was bad"
)),
_ => Ok(()),
}
}
pub fn check_entry_size(
declared: Option<u64>,
produced: u64,
name: &str,
kind: &EntryKind,
) -> Result<(), String> {
if matches!(kind, EntryKind::Symlink { .. }) {
return Ok(());
}
match declared {
Some(d) if d != produced => Err(format!(
"entry {name:?} declared {d} bytes but produced {produced}"
)),
_ => Ok(()),
}
}
pub fn check_entry_count(
declared: Option<usize>,
enumerated: usize,
report: &FidelityReport,
) -> Result<(), String> {
let Some(d) = declared else { return Ok(()) };
if d == enumerated {
return Ok(());
}
if report
.warnings
.iter()
.any(|w| matches!(w, Fidelity::EntryCountMismatch { .. }))
{
return Ok(());
}
Err(format!(
"the index declares {d} entries but {enumerated} were enumerated, and the \
fidelity report does not carry an EntryCountMismatch warning saying so"
))
}
pub fn check_fidelity_claim(report: &FidelityReport, approximated: bool) -> Result<(), String> {
if report.is_lossless() && approximated {
return Err(format!(
"the report claims nothing was approximated ({:?}, no warnings) \
but something was",
report.rung
));
}
Ok(())
}
#[cfg(test)]
mod broken_honesty {
use super::*;
use crate::Error;
use crate::fidelity::Rung;
#[test]
fn an_io_error_from_hostile_bytes_is_refused_because_it_exits_one() {
let e = Error::Io(std::io::Error::other("boom"));
let msg = check_error_is_classified(&e).expect_err("exit 1 must be refused");
assert!(
msg.contains("exit code 1"),
"the failure must name why: {msg}"
);
}
#[test]
fn corrupt_unsupported_and_resource_limit_are_all_permitted() {
for e in [
Error::Corrupt("bad".into()),
Error::Unsupported("nope".into()),
Error::ResourceLimit("too big".into()),
Error::NotSeekable {
format: crate::FormatId::new("zip"),
},
] {
assert!(
check_error_is_classified(&e).is_ok(),
"{e:?} must be permitted"
);
}
}
#[test]
fn a_declared_size_that_disagrees_with_bytes_produced_is_refused() {
let msg = check_entry_size(Some(100), 42, "a.txt", &EntryKind::File)
.expect_err("mismatch must fail");
assert!(msg.contains("a.txt"), "must name the entry: {msg}");
assert!(
msg.contains("100") && msg.contains("42"),
"must name both numbers: {msg}"
);
assert!(check_entry_size(None, 42, "a.txt", &EntryKind::File).is_ok());
assert!(check_entry_size(Some(42), 42, "a.txt", &EntryKind::File).is_ok());
for kind in [EntryKind::Dir, EntryKind::Other] {
assert!(
check_entry_size(Some(100), 42, "a.txt", &kind).is_err(),
"{kind:?} must stay in scope"
);
}
}
#[test]
fn a_symlink_whose_target_was_consumed_eagerly_is_permitted() {
let link = EntryKind::Symlink {
target: "real.txt".into(),
};
assert!(
check_entry_size(Some(8), 0, "link.txt", &link).is_ok(),
"a symlink's target is read by the container, not by the caller"
);
}
#[test]
fn a_declared_count_that_disagrees_with_entries_enumerated_is_refused() {
let silent = FidelityReport::new(Rung::Exact);
let msg = check_entry_count(Some(8), 6, &silent).expect_err("mismatch must fail");
assert!(
msg.contains("8") && msg.contains("6"),
"must name both: {msg}"
);
assert!(check_entry_count(None, 6, &silent).is_ok());
assert!(check_entry_count(Some(6), 6, &silent).is_ok());
}
#[test]
fn a_declared_count_mismatch_the_report_owns_up_to_is_permitted() {
let mut declared = FidelityReport::new(Rung::Exact);
declared.warn(Fidelity::EntryCountMismatch {
format: crate::FormatId::new("zip"),
declared: 8,
enumerated: 6,
reason: "records sharing a name collapse into one".into(),
});
assert!(
check_entry_count(Some(8), 6, &declared).is_ok(),
"a shortfall the report owns up to is a supported outcome, not a violation"
);
let mut unrelated = FidelityReport::new(Rung::Exact);
unrelated.warn(Fidelity::MetadataIncomplete {
entry: "a.txt".into(),
fields: crate::MetaFields::default(),
});
assert!(
check_entry_count(Some(8), 6, &unrelated).is_err(),
"an unrelated warning does not declare a count shortfall"
);
}
#[test]
fn claiming_exact_with_no_warnings_while_approximating_is_refused() {
let clean = FidelityReport::new(Rung::Exact);
let msg = check_fidelity_claim(&clean, true).expect_err("a false claim must fail");
assert!(
msg.contains("approximated"),
"must say what was claimed: {msg}"
);
assert!(check_fidelity_claim(&clean, false).is_ok());
}
}