1use std::fmt;
4use std::sync::Mutex;
5
6use crate::object::ObjId;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum DiagnosticKind {
11 JunkBeforeHeader,
13 MissingHeader,
15 BadStartxref,
17 BadXrefSection,
19 BadXrefEntry,
21 XrefRebuilt,
23 TrailerRecovered,
25 XrefLoop,
27 BadStreamLength,
29 MissingEndobj,
31 ObjectIdMismatch,
33 TruncatedStream,
35 UnknownFilter(Vec<u8>),
37 SyntaxError,
39 BadPageTree,
41 PagesRecovered,
43 Other(String),
45}
46
47impl fmt::Display for DiagnosticKind {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self {
50 Self::JunkBeforeHeader => f.write_str("junk before header"),
51 Self::MissingHeader => f.write_str("missing header"),
52 Self::BadStartxref => f.write_str("bad startxref"),
53 Self::BadXrefSection => f.write_str("bad xref section"),
54 Self::BadXrefEntry => f.write_str("bad xref entry"),
55 Self::XrefRebuilt => f.write_str("xref rebuilt by scanning"),
56 Self::TrailerRecovered => f.write_str("trailer recovered"),
57 Self::XrefLoop => f.write_str("xref /Prev loop"),
58 Self::BadStreamLength => f.write_str("wrong stream /Length"),
59 Self::MissingEndobj => f.write_str("missing endobj"),
60 Self::ObjectIdMismatch => f.write_str("object id mismatch"),
61 Self::TruncatedStream => f.write_str("truncated stream"),
62 Self::UnknownFilter(name) => {
63 write!(f, "unsupported filter /{}", String::from_utf8_lossy(name))
64 }
65 Self::SyntaxError => f.write_str("syntax error"),
66 Self::BadPageTree => f.write_str("bad page tree node"),
67 Self::PagesRecovered => f.write_str("pages recovered by scanning"),
68 Self::Other(msg) => f.write_str(msg),
69 }
70 }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct Diagnostic {
76 pub kind: DiagnosticKind,
78 pub offset: Option<u64>,
80 pub object: Option<ObjId>,
82}
83
84impl fmt::Display for Diagnostic {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 write!(f, "{}", self.kind)?;
87 if let Some(id) = self.object {
88 write!(f, " (object {} {})", id.num, id.generation)?;
89 }
90 if let Some(offset) = self.offset {
91 write!(f, " at byte {offset}")?;
92 }
93 Ok(())
94 }
95}
96
97#[derive(Debug, Default)]
100pub struct Diagnostics {
101 list: Mutex<Vec<Diagnostic>>,
102}
103
104const MAX_DIAGNOSTICS: usize = 10_000;
106
107impl Diagnostics {
108 pub fn push(&self, kind: DiagnosticKind, offset: Option<u64>, object: Option<ObjId>) {
110 if let Ok(mut list) = self.list.lock()
111 && list.len() < MAX_DIAGNOSTICS
112 {
113 list.push(Diagnostic {
114 kind,
115 offset,
116 object,
117 });
118 }
119 }
120
121 pub fn snapshot(&self) -> Vec<Diagnostic> {
123 self.list.lock().map(|l| l.clone()).unwrap_or_default()
124 }
125
126 pub fn len(&self) -> usize {
128 self.list.lock().map(|l| l.len()).unwrap_or(0)
129 }
130
131 pub fn is_empty(&self) -> bool {
133 self.len() == 0
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn records_and_displays() {
143 let d = Diagnostics::default();
144 assert!(d.is_empty());
145 d.push(
146 DiagnosticKind::BadStreamLength,
147 Some(10),
148 Some(ObjId::new(4, 0)),
149 );
150 d.push(DiagnosticKind::UnknownFilter(b"Foo".to_vec()), None, None);
151 let all = d.snapshot();
152 assert_eq!(all.len(), 2);
153 assert_eq!(
154 all[0].to_string(),
155 "wrong stream /Length (object 4 0) at byte 10"
156 );
157 assert_eq!(all[1].to_string(), "unsupported filter /Foo");
158 }
159
160 #[test]
161 fn capped() {
162 let d = Diagnostics::default();
163 for _ in 0..MAX_DIAGNOSTICS + 5 {
164 d.push(DiagnosticKind::SyntaxError, None, None);
165 }
166 assert_eq!(d.len(), MAX_DIAGNOSTICS);
167 }
168}