use std::fmt;
use std::sync::Mutex;
use crate::object::ObjId;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DiagnosticKind {
JunkBeforeHeader,
MissingHeader,
BadStartxref,
BadXrefSection,
BadXrefEntry,
XrefRebuilt,
TrailerRecovered,
XrefLoop,
BadStreamLength,
MissingEndobj,
ObjectIdMismatch,
TruncatedStream,
UnknownFilter(Vec<u8>),
SyntaxError,
BadPageTree,
PagesRecovered,
Other(String),
}
impl fmt::Display for DiagnosticKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::JunkBeforeHeader => f.write_str("junk before header"),
Self::MissingHeader => f.write_str("missing header"),
Self::BadStartxref => f.write_str("bad startxref"),
Self::BadXrefSection => f.write_str("bad xref section"),
Self::BadXrefEntry => f.write_str("bad xref entry"),
Self::XrefRebuilt => f.write_str("xref rebuilt by scanning"),
Self::TrailerRecovered => f.write_str("trailer recovered"),
Self::XrefLoop => f.write_str("xref /Prev loop"),
Self::BadStreamLength => f.write_str("wrong stream /Length"),
Self::MissingEndobj => f.write_str("missing endobj"),
Self::ObjectIdMismatch => f.write_str("object id mismatch"),
Self::TruncatedStream => f.write_str("truncated stream"),
Self::UnknownFilter(name) => {
write!(f, "unsupported filter /{}", String::from_utf8_lossy(name))
}
Self::SyntaxError => f.write_str("syntax error"),
Self::BadPageTree => f.write_str("bad page tree node"),
Self::PagesRecovered => f.write_str("pages recovered by scanning"),
Self::Other(msg) => f.write_str(msg),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub kind: DiagnosticKind,
pub offset: Option<u64>,
pub object: Option<ObjId>,
}
impl fmt::Display for Diagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(id) = self.object {
write!(f, " (object {} {})", id.num, id.generation)?;
}
if let Some(offset) = self.offset {
write!(f, " at byte {offset}")?;
}
Ok(())
}
}
#[derive(Debug, Default)]
pub struct Diagnostics {
list: Mutex<Vec<Diagnostic>>,
}
const MAX_DIAGNOSTICS: usize = 10_000;
impl Diagnostics {
pub fn push(&self, kind: DiagnosticKind, offset: Option<u64>, object: Option<ObjId>) {
if let Ok(mut list) = self.list.lock()
&& list.len() < MAX_DIAGNOSTICS
{
list.push(Diagnostic {
kind,
offset,
object,
});
}
}
pub fn snapshot(&self) -> Vec<Diagnostic> {
self.list.lock().map(|l| l.clone()).unwrap_or_default()
}
pub fn len(&self) -> usize {
self.list.lock().map(|l| l.len()).unwrap_or(0)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn records_and_displays() {
let d = Diagnostics::default();
assert!(d.is_empty());
d.push(
DiagnosticKind::BadStreamLength,
Some(10),
Some(ObjId::new(4, 0)),
);
d.push(DiagnosticKind::UnknownFilter(b"Foo".to_vec()), None, None);
let all = d.snapshot();
assert_eq!(all.len(), 2);
assert_eq!(
all[0].to_string(),
"wrong stream /Length (object 4 0) at byte 10"
);
assert_eq!(all[1].to_string(), "unsupported filter /Foo");
}
#[test]
fn capped() {
let d = Diagnostics::default();
for _ in 0..MAX_DIAGNOSTICS + 5 {
d.push(DiagnosticKind::SyntaxError, None, None);
}
assert_eq!(d.len(), MAX_DIAGNOSTICS);
}
}